Files
directus/.planning/phase-1-slice-1-schema/01-project-scaffold.md
T
julian 387c3c4cfa Task 1.1 — Project scaffold
Phase 1 task 1.1 lands. Directus 11.17.4 boots locally end-to-end
against a TimescaleDB+PostGIS container; admin UI serves at :8055,
admin bootstrap from env vars works, named volumes preserve data
across down/up cycles.

Scaffold:
- Dockerfile — FROM directus/directus:11.17.4. Pre-installs
  postgresql16-client (ahead of task 1.2's db-init runner needing psql).
  Bakes in /directus/snapshots, /directus/db-init, /directus/scripts,
  /directus/extensions, /directus/entrypoint.sh.
- compose.dev.yaml — db (timescale/timescaledb-ha:pg16.6-ts2.17.2-all)
  + directus (local build), healthchecks, named volumes
  directus-pg-data + directus-uploads.
- entrypoint.sh — placeholder using upstream's actual flow
  (node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs);
  the real db-init -> schema apply -> start wrapper lands in task 1.7.
- package.json — scripts-only (dev, dev:down, dev:reset,
  schema:snapshot, schema:apply, db:init), no runtime deps.
- .env.example — sectioned, fully documented, KEY/SECRET marked
  required with generation hints.
- .gitignore, .dockerignore — match the processor service conventions.
- snapshots/, db-init/, scripts/, extensions/ — empty with .gitkeep,
  filled by later Phase 1 tasks (1.3, 1.6) and Phase 5.

Lessons locked in (against the empirical pnpm dev boot):
- timescale/timescaledb-ha:pg16-latest does NOT exist on Docker Hub.
  Pin a concrete version (we used pg16.6-ts2.17.2-all).
- This image's data directory is /home/postgres/pgdata/data, not
  /pgdata or /var/lib/postgresql/data. PGDATA env var and the volume
  mount must both target it.
- The -all variant bundles PostGIS binaries but the extension is not
  auto-created on the directus database; CREATE EXTENSION lands in
  Phase 2 alongside the geofences/SLZs/waypoints collections.
- The upstream image's CMD is bootstrap + pm2-runtime, not a simple
  cli.js start. Bypassing pm2 would lose crash recovery.

These corrections folded into 01-project-scaffold.md (deliverable line
+ Done section), 08-gitea-ci-dryrun.md (CI service tag), and the
inline comments in compose.dev.yaml so future implementers don't
re-discover them.

Status: ROADMAP marks 1.1 done, Phase 1 in progress, 1.2 next.
2026-05-01 21:29:13 +02:00

7.5 KiB

Task 1.1 — Project scaffold

Phase: 1 — Slice 1 schema + deploy pipeline Status: 🟩 Done Depends on: None Wiki refs: docs/wiki/entities/directus.md, docs/wiki/synthesis/directus-schema-draft.md

Goal

Initialize the directus/ service folder with the directory layout from the Phase 1 README, the config files needed for local Docker compose dev, and a minimal compose.dev.yaml that boots Directus + TimescaleDB so the next tasks have something to iterate against. No Directus collections are created in this task — that starts in 1.4.

Deliverables

  • directus/DockerfileFROM directus/directus:11.x, copies snapshots/, db-init/, scripts/, entrypoint.sh, extensions/ into the image. Sets ENTRYPOINT ["/directus/entrypoint.sh"]. (Concrete entrypoint contents land in task 1.7; for now create a placeholder that just execs the upstream entrypoint.)
  • directus/compose.dev.yaml — two services:
    • db: timescale/timescaledb-ha:pg16.6-ts2.17.2-all (the -all variant bundles PostGIS binaries; the :pg16-latest floating tag does NOT exist on Docker Hub — pin a concrete TS+PG version). Volume-mapped Postgres data dir, healthcheck.
    • directus: built from local Dockerfile, depends on db healthy, env vars for DB connection + KEY + SECRET + admin bootstrap, port 8055 exposed.
  • directus/package.json — minimal, only for npm scripts (no runtime deps). Scripts:
    • schema:snapshotbash scripts/schema-snapshot.sh (script body lands in 1.6)
    • schema:applybash scripts/schema-apply.sh
    • db:initbash scripts/apply-db-init.sh
    • devdocker compose -f compose.dev.yaml up --build
    • dev:downdocker compose -f compose.dev.yaml down
    • dev:resetdocker compose -f compose.dev.yaml down -v && docker compose -f compose.dev.yaml up --build
  • directus/.env.example — full list of env vars with descriptions and defaults. Required: DB_HOST, DB_PORT, DB_DATABASE, DB_USER, DB_PASSWORD, KEY, SECRET, ADMIN_EMAIL, ADMIN_PASSWORD, PUBLIC_URL. Plus optional: LOG_LEVEL, LOG_STYLE, CACHE_ENABLED, CORS_ENABLED, CORS_ORIGIN, WEBSOCKETS_ENABLED.
  • directus/.gitignorenode_modules/, .env, .env.local, *.log, directus-data/ (the local Postgres volume mount, if used).
  • directus/.dockerignore.git/, .planning/, node_modules/, .env*, *.md except README.md, compose.dev.yaml (compose isn't part of the image), directus-data/.
  • Empty placeholder directories with .gitkeep:
    • snapshots/ (1.6 fills it)
    • db-init/ (1.3 fills it)
    • scripts/ (1.2, 1.6 fill it)
    • extensions/ (Phase 5)
  • directus/entrypoint.sh — placeholder that simply exec /directus/cli.js start (or whatever the upstream image's start command is). Real wrapper lands in 1.7.
  • directus/README.md already exists from this scaffold pass — verify it's accurate.

Specification

  • Postgres image choice. Pin to a TimescaleDB image that includes PostgreSQL 16. PostGIS will be installed via db-init/ in Phase 2; the base image must support CREATE EXTENSION postgis (most TimescaleDB-HA images do). Document the pinned tag in compose.dev.yaml.
  • Volume policy in compose.dev.yaml. Use a named volume (directus-pg-data) so dev:down preserves data and dev:reset wipes it.
  • No secrets committed. .env is gitignored. .env.example carries placeholder values only.
  • No bind mounts of snapshots/ or db-init/ in compose.dev.yaml. The image bakes them in. (Implementer can override with a bind mount during local iteration but the committed file does not.)
  • Entrypoint is a placeholder in this task. Real flow (db-init → schema apply → start) lands in 1.7. Keep the placeholder simple to unblock 1.4 testing.

Acceptance criteria

  • pnpm install succeeds (no runtime deps; lockfile generated).
  • docker compose -f compose.dev.yaml up --build boots Directus successfully against a fresh TimescaleDB container.
  • http://localhost:8055 serves the Directus admin login.
  • First-time bootstrap with ADMIN_EMAIL / ADMIN_PASSWORD from .env works.
  • pnpm dev:down stops the stack, preserves the volume.
  • pnpm dev:reset wipes the volume and reboots clean.
  • No collection definitions exist yet — the Directus instance is empty by design.

Risks / open questions

  • TimescaleDB-HA image PostGIS support. Verify the chosen tag includes postgis extension binaries (or document the alternative — e.g. switching to postgis/postgis:16-master with manual TimescaleDB install). Capture the answer in this task's Done section.
  • Directus 11.x patch version. Pin a specific tag (e.g. 11.5.1) rather than 11.x for reproducible builds. Update the pin via PR when bumping.

Done

Pending commit by user. All deliverables created in the same working tree pass.

Open question resolutions (corrected against live pnpm dev boot 2026-05-01):

  • TimescaleDB-HA image tag. :pg16-latest does not exist on Docker Hub (the agent's initial pin failed at pull time). The empirically-verified tag is timescale/timescaledb-ha:pg16.6-ts2.17.2-all. The -all suffix bundles PostGIS binaries.
  • PGDATA path. Not /pgdata (the agent's first guess); the actual data directory baked into this image is /home/postgres/pgdata/data. PGDATA: /pgdata plus a volume mount to /pgdata produced "could not change permissions of directory" errors at initdb. Fixed by setting both PGDATA and the volume target to /home/postgres/pgdata/data.
  • PostGIS extension. Binaries are bundled in the -all image but the extension is not auto-created on the directus database. Directus boot logs warn: PostGIS isn't installed. Geometry type support will be limited. Resolution: CREATE EXTENSION IF NOT EXISTS postgis; lands in db-init when geometry types are needed (Phase 2). Phase 1 has no geometry columns so the warning is benign.
  • Directus version pin: directus/directus:11.17.4 confirmed to exist on Docker Hub. Used as the image pin.

Deviations from task spec:

  1. entrypoint.sh delegates to node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs (the upstream image's actual CMD) rather than exec /directus/cli.js start. The upstream image uses pm2-runtime to manage the process; bypassing it would skip crash recovery and signal handling that pm2 provides. The bootstrap step is idempotent (safe to run every boot) and handles admin user creation.
  2. compose.dev.yaml sets PGDATA: /home/postgres/pgdata/data and mounts the named volume to the same path. Required by the timescaledb-ha:*-all image; mounting elsewhere fails initdb.
  3. Dockerfile installs postgresql16-client via apk so that scripts/apply-db-init.sh (task 1.2) can invoke psql without adding that dependency later.
  4. README.md updated: pinned 11.x11.17.4; CI section notes workflow file is pending (task 1.8).

Live boot acceptance (2026-05-01):

pnpm dev against fresh volumes succeeded: db became healthy, Directus ran 60+ system migrations, PM2 cluster started, server bound at http://0.0.0.0:8055, GraphQL Subscriptions and WebSocket Server started. Admin bootstrap (creating first admin role + admin user) completed. One benign warning ("PostGIS isn't installed" — see Open question resolutions above). One harmless migration warning about a non-existent constraint on directus_comments_collection_foreign (a Directus internal migration's expected idempotent guard, not caused by this task). All Phase 1 task 1.1 acceptance criteria met.