Local development with Docker, DDEV, and Lando
K0smos provides three local entry points around the same PHP 8.5 application contract. Docker Compose remains the deployment-shaped reference; DDEV uses the maintained FrankenPHP add-on; Lando builds the repository's own FrankenPHP image instead of maintaining another PHP/Caddy stack.
Parity matrix
| Capability | Docker Compose | DDEV | Lando |
|---|---|---|---|
| PHP/web | dunglas/frankenphp:1-php8.5-trixie |
PHP 8.5 ZTS + FrankenPHP add-on | Builds docker/frankenphp/Dockerfile |
| Docroot | /app/public |
${DDEV_APPROOT}/public |
/app/public through the shared Caddyfile |
| Required extensions | Shared Docker extension list; all three DB drivers | Add-on ZTS packages; checked by the shared script | Same image/check as Docker |
| Database default | Tenant-selected; Docker example can use SQLite | Isolated var/db/local-development.sqlite; DDEV DB remains available |
Same isolated SQLite file |
| DB variants | SQLite, MariaDB/MySQL, PostgreSQL | DDEV-managed MariaDB/MySQL/PostgreSQL | External/custom local service via .lando.local.yml |
| Redis | Redis 7 persistent volume | Redis 7 add-on service | Redis 7 Alpine persistent volume |
| Composer | Composer 2 in PHP image | Composer 2 | Composer 2 from shared image |
| Node/Vite | Host/operator | Node 22 in DDEV web | Dedicated Node 22 service |
| Queue | Optional Compose worker profile | CLI in web service | lando worker tooling |
| Scheduler | Operator/cron invocation | CLI in web service | lando schedule tooling |
| Xdebug | Installed, XDEBUG_MODE=off |
Add-on package, ddev xdebug on/off |
Installed, XDEBUG_MODE=off |
| Local TLS/host | http://localhost:8080 by default |
https://k0smos.ddev.site |
https://k0smos.lndo.site |
| Source/DB volumes | Bind mount + named Redis volume | DDEV project mount + named services | Lando project mount + named Redis volume |
The committed DDEV FrankenPHP files are the generated output of
ddev/ddev-frankenphp v1.1.5 at commit
8587c8311c8bbd256330e2f059e43d10258a6e48; the Redis service shape tracks
ddev/ddev-redis v2.2.0 at commit
b50ac777feb8816f1dd41d21c6a0a2413045dd37. Re-run the add-on update in a
dedicated change and validate the extension matrix before accepting generated
diffs. DDEV requires v1.24.10 or newer for those add-ons.
Secrets before first start
Never add a secret to .ddev/config.yaml, .lando.yml, app.env, or a tenant
JSON file. Put machine-local values in the already ignored .env.local:
APP_ENV=development
APP_DEBUG=true
TENANT_ENV=localhost
JWT_SECRET=<64-or-more-random-hex-characters>
K0SMOS_SECRET_CURRENT_KEY_ID=local-v1
K0SMOS_SECRET_KEYS={"local-v1":"<base64-encoded-32-byte-key>"}
Generate values locally with php -r and random_bytes(); do not paste the
result into tickets, logs, screenshots, or commits. The full key rotation
procedure remains in doc/public/en/security/secret-management.md.
Shared safety contract
tools/local-env/bootstrap.sh is idempotent: it installs Composer dependencies,
checks PHP/extensions/configuration, and applies migrations to the local
database. It refuses to run unless APP_ENV=development, TENANT_ENV=localhost,
and the DB is either under var/db or on an allowlisted local container host.
It also rejects K0SMOS_DB_URL, preventing a stale remote DSN from receiving
local migrations.
tools/local-env/smoke.sh checks CLI bootstrap, HTTP health, queue topology and
an empty local-smoke worker queue. --tests runs the Application suite;
tests/bootstrap.php forces an isolated temporary SQLite database. --skip-http
is available only for configuration validation before the web process starts.
Validation status — 2026-09-14
On a Linux host with PHP 8.5.10, the shared configuration check passed and a
fresh, throwaway SQLite file under var/db completed all 100 migrations. The
same isolated file served a Default HTTP 200 home page and its compiled JS
asset with HTTP 200; smoke.sh --skip-http passed CLI and queue probes. Two
focused PHPUnit classes passed 15 tests and 336 assertions using the disposable
test bootstrap. The optional --tests smoke path starts the full Application
inventory; that broad run was stopped during this bounded validation and is
not counted as passing. The safety guard rejected both APP_ENV=production and a
non-empty URL DSN before bootstrap. These checks exercise the shared scripts
and host runtime only; they are not evidence that DDEV or Lando boots, restarts,
rebuilds, maps permissions, or works on macOS/Windows. Neither CLI is installed
on this validation host. The project owner explicitly confirmed on 2026-09-14
that DDEV and Lando have not been tested. Their representative-platform
lifecycle, database variant, queue and permission behavior remain unverified;
environment parity is therefore not claimed.
Docker Compose:
docker compose up -d app redis
docker compose exec app ./tools/local-env/bootstrap.sh
docker compose exec app ./tools/local-env/smoke.sh
docker compose --profile worker up -d worker worker-notifications
DDEV:
ddev start
ddev exec ./tools/local-env/bootstrap.sh
ddev exec ./tools/local-env/smoke.sh
ddev exec ./tools/local-env/smoke.sh --tests
ddev xdebug on
ddev xdebug off
Use ddev stop, ddev restart, and ddev restart --no-cache for the normal
lifecycle. ddev delete removes the local environment and its database; export
anything valuable first. To exercise a DDEV-managed database, keep the chosen
database type in ignored .ddev/config.local.yaml and add matching discrete
K0SMOS_DB_* values to ignored .ddev/.env.web. The bootstrap allowlist accepts
only db, database, localhost, or 127.0.0.1 and never accepts a URL DSN.
Lando:
lando start
lando bootstrap
lando smoke
lando test
lando worker
lando schedule
Use lando stop, lando start, and lando rebuild for lifecycle changes.
lando destroy removes the environment and named Redis volume. To enable
Xdebug for web requests, create ignored .lando.local.yml, override
services.appserver.services.environment.XDEBUG_MODE to debug, then rebuild.
Use the same local override mechanism for a MariaDB/MySQL/PostgreSQL service;
keep all credentials local and preserve the bootstrap database-host allowlist.
On macOS and Windows, keep the repository inside the filesystem recommended by the selected container runtime and let DDEV/Lando manage UID/GID mapping. Do not solve permission errors with recursive world-writable permissions. WSL2 users should keep the checkout in the Linux filesystem. After changing an image, add-on, PHP extension, or mount, perform a no-cache DDEV restart or Lando rebuild and rerun bootstrap plus smoke.
Frontend themes
The agent does not run theme builds. The project owner installs and builds each affected theme manually, for example:
ddev npm --prefix template/default install
ddev npm --prefix template/default run build
lando npm --prefix template/default install
lando npm --prefix template/default run build
Repeat for sober, cc1, dm1, lts1, pc1, pc2, and pc3 when their sources or
shared frontend policy change, then run the committed manifest/budget checks.
Upstream references
- DDEV FrankenPHP add-on: https://addons.ddev.com/addons/ddev/ddev-frankenphp
- DDEV Redis add-on: https://addons.ddev.com/addons/ddev/ddev-redis
- DDEV database types: https://docs.ddev.com/en/stable/users/extend/database-types/
- Lando custom API 3 service: https://docs.lando.dev/services/lando-3.html
- Lando tooling: https://docs.lando.dev/landofile/tooling.html
- Lando proxy/TLS behavior: https://docs.lando.dev/landofile/proxy.html