Skip to content
24/48h shipping across Italy
Skip to content
Documentation navigation

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