Stage 2 was php:8.3-apache (Debian), which compiles PHP from source with --with-apxs2 for mod_php -- that base image alone is 719MB of our 735MB, before any app code. Replaced with alpine:3.24 + apk's own prebuilt php83/php83-apache2/apache2 packages: same architecture (one process, mod_php, .htaccess-driven rewriting), no fpm/nginx rewrite needed. - docker/apache.conf: rewritten for Alpine's apache2 (mod_rewrite ships but isn't loaded by default; a different default document root/log paths). Logs redirected to stdout/stderr so `docker logs` still shows them -- Alpine's own defaults write to a real file under ServerRoot, unlike the official Debian image's symlinked paths. - docker/entrypoint.sh: su-exec instead of su -- BusyBox's su doesn't take the same -c/user argument order as the GNU one the previous entrypoint relied on. Also moved earlier in the Dockerfile (with the other rarely-changing setup, before COPY . .) so it no longer re-runs on every build for a file that essentially never changes. - Composer's binary is still borrowed from the official composer:2 image via multi-stage COPY, not apk's own `composer` package, which turned out to pull in an entire second PHP interpreter (php85) as a dependency just to run itself. - ext-iconv needed adding explicitly (symfony/polyfill-mbstring depends on it; the official Debian image bundles it by default, apk doesn't). Verified against the real compose stack, not just that it builds: apk install; composer install; migrations on startup; PHPUnit 88/88 (runs on the host, but confirms nothing else broke); and by hand, all through the actual container -- health check, SPA fallback for unknown routes, static assets served directly, the API's 401 guard, and a full magic-link -> verify -> JWT -> authenticated project create/list round trip via the real Mailpit catcher. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PHP Project Manager
A small project-management app: each user owns projects, and each project holds cards you can organise as a flat list or a drag-and-drop kanban board. It's a REST API in PHP (Slim 4) backed by SQLite, with a Vue 3
- TypeScript PWA frontend. There's no password — signing in is a link emailed to you, which creates your account the first time, and you can add a passkey afterwards for a quicker sign-in next time.
Documentation
This page covers running the app. For anything more technical — the REST API, configuration, running without Docker, project layout, and the development history — see docs/.
Getting started
The only requirement is Docker with the Compose plugin.
docker compose up -d
Then open http://localhost:8080. A Mailpit mail-catcher also starts alongside the app, at http://localhost:8025 — since a default local setup has nowhere else to send the sign-in emails.
First-time login
- Enter any email address and submit. There's no separate sign-up step and no password to choose — this both creates your account and sends it a sign-in link.
- Open http://localhost:8025 (Mailpit) instead of a real inbox, and open the message that just arrived there.
- Click the link inside it. You're now signed in, on a new, already-verified account.
From then on, your profile page (top right, your email address) lets you add a passkey — your device's fingerprint, face, or PIN — so you don't need to wait on an email to sign in next time.
Everything the app stores (your account, projects, cards) lives in a Docker
volume, so it survives docker compose restart / down + up;
docker compose down -v wipes it for a clean slate. See
docs/setup.md for configuration, running without Docker,
and running the test suite.
Provenance
This project was generated with Claude Code.