Self-hosters shouldn't be stuck with a hardcoded 100-project limit; MAX_PROJECTS_PER_OWNER now controls it, defaulting to 0 (unlimited). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.0 KiB
Setup & configuration
For the quick version — just running the app — see the root README. This covers the rest: running without Docker, every configuration option, and the test suite.
Run with Docker
The only requirement is Docker with the Compose plugin.
docker compose up -d
This runs a multi-stage build — a Node stage compiles the Vue frontend, then a PHP 8.3 + Apache stage (Alpine-based; apk's own prebuilt packages rather than compiling PHP from source, which is most of why the image is ~90MB rather than several times that) bakes in the PHP source and the built SPA — applies migrations, and serves the whole app at http://localhost:8080: the SPA at / (assets and all) and the REST API under /api (e.g. curl http://localhost:8080/api/health). Unknown paths fall back to the SPA shell for client-side routing.
- A Mailpit container (the maintained MailHog successor — one ~15 MB Go binary, messages kept in memory) also starts. The API sends all email to it; read it at http://localhost:8025. Set
MAIL_TRANSPORT=mailor=smtp(withMAIL_SMTP_*) to send for real. - The image is the artifact: PHP source and the compiled frontend are copied in at build time, not bind-mounted. Rebuild to pick up any code change:
docker compose up -d --build. For iterating on the frontend, run the Vite dev server on the host instead (see web/README.md). - The SQLite database and the generated JWT signing key live in the
storagenamed volume, mounted at/var/www/storage, so they survivedocker compose restart/down+up. docker compose down -vremoves the volume and gives you a clean database.- Override settings via the environment or a
.envfile in the repo root (Compose substitutesAPP_DEBUG,JWT_SECRET,JWT_TTL— see docker-compose.yml).
Run without Docker
Requires PHP 8.1+ with the pdo_sqlite and mbstring extensions, plus Composer. On Fedora:
sudo dnf install php-cli php-pdo php-mbstring composer
Setup
composer install
cp .env.example .env # optional; sane defaults are used without it
composer migrate # creates storage/database.sqlite and its tables
Running
composer serve # http://localhost:8080 (php -S localhost:8080 -t public)
Any web server can serve the API as long as the document root is public/ and unknown paths fall through to public/index.php. public/.htaccess also serves a built frontend from public/ (copy web/dist/ there) and only falls back to index.php for /api and when no index.html is present.
The frontend itself still needs its own toolchain — see web/README.md.
Configuration
All settings are optional environment variables (read from .env or the real environment). See .env.example.
| Variable | Default | Purpose |
|---|---|---|
APP_DEBUG |
false |
Include exception details in error responses |
DATABASE_PATH |
storage/database.sqlite |
SQLite file location |
JWT_SECRET |
auto-generated into storage/secret.key |
Token signing key |
JWT_TTL |
86400 |
Token lifetime in seconds |
APP_ALLOW_REGISTRATION |
true |
When false, a magic link is only ever sent to an existing address — an unknown one is silently ignored, so no new accounts get created |
MAGIC_LINK_RESEND_SECONDS |
60 |
Minimum gap before a magic link can be resent to the same address (sign-in or email-change). Docker Compose overrides this to 0, so links resend immediately in development |
MAX_PROJECTS_PER_OWNER |
0 |
Maximum number of projects a single user may create. 0 means unlimited |
APP_URL |
http://localhost:8080 |
Base URL used to build magic links (http://localhost:5173 for a host npm run dev) |
WEBAUTHN_RP_ID |
APP_URL's host |
Passkey relying party ID (domain). Must be localhost or a real domain over HTTPS — a LAN IP won't work |
WEBAUTHN_RP_NAME |
Projects |
Passkey relying party display name, shown in the browser/OS prompt |
MAIL_TRANSPORT |
mail |
mail (PHP mail()), smtp, or log (append to a file) |
MAIL_FROM / MAIL_FROM_NAME |
no-reply@todo.test / Projects |
Envelope sender |
MAIL_LOG_PATH |
storage/mail.log |
Where log transport writes |
MAIL_SMTP_HOST / _PORT / _USERNAME / _PASSWORD / _ENCRYPTION |
— / 587 / — / — / tls |
Used only when MAIL_TRANSPORT=smtp |
Standalone, SMTP is opt-in and the API otherwise falls back to PHP's mail(). Under Docker Compose the default is MAIL_TRANSPORT=smtp pointed at the bundled Mailpit container (mailpit:1025, no auth/TLS); open http://localhost:8025 to read what was "sent".
Tests
composer install # installs phpunit (require-dev)
vendor/bin/phpunit
Each test run applies every file in migrations/*.sql to a fresh SQLite database, so it always exercises the current schema from scratch.