2026-10-06_identity-infra-compose_spec
Spec — Wine2You Identity Stack: Infra Compose (task 1)
Date: 2026-10-06
Repo: digid-co/wine2you
Target host: Oracle OCI wine2you-dev-zrh (ARM aarch64, 2 OCPU / 12 GB, Oracle Linux 9.8, Docker 29.8.2, /opt/compose)
1. Goal & Boundary
Deliverable: a self-contained Docker Compose stack that brings up the four backing services of the Wine2You identity/authorization/API-key architecture, version-pinned and ARM-ready, with a bootstrap script and a README. No application code in this task.
In scope:
infra/identity/compose.yaml— Postgres, ZITADEL, OpenFGA, Unkeyinfra/identity/.env.example— every required variable, no secrets committedinfra/identity/bootstrap/— init SQL for the extra databases (openfga, module), and any first-boot helperinfra/identity/README.md— bring-up, ports, verification commands.gitignoreadditions (infra/identity/.env)
Out of scope (do NOT build here):
- The
user-mgmtapplication module (separate task 2). - Medusa / Next.js storefront / Meilisearch.
- Cloudflare Tunnel, DNS, TLS, reverse proxy.
- Any ZITADEL/OpenFGA/Unkey content (orgs, roles, models, keys) — only the services.
2. Type & API Contracts
Required services, ports, images (ARM64-compatible; pin exact tags):
| Service | Image (pin) | Ports (host→container) | Persistence |
|---|---|---|---|
postgres |
postgres:16-alpine |
127.0.0.1:5432→5432 | named volume pgdata |
zitadel |
ghcr.io/zitadel/zitadel:latest (pin to a concrete tag at build time; ARM) |
127.0.0.1:8080→8080 | uses postgres |
openfga |
openfga/openfga:latest (pin concrete tag; ARM) |
127.0.0.1:8081→8080 (http), 127.0.0.1:8082→8081 (grpc) | uses postgres |
unkey |
record the chosen self-host path + pinned images in the README (Unkey self-host uses multiple images: api/dashboard/worker + its own DB) | 127.0.0.1:8083→8080 | its own DB (MySQL or libSQL) |
Networking: one user-defined bridge network w2y-identity. All host port bindings are
127.0.0.1-only (nothing publicly exposed; the box reaches them via tailnet/SSH).
restart: unless-stopped on every service. Healthchecks on postgres and openfga minimum.
Postgres databases to create (via bootstrap/init-databases.sql or ZITADEL's own init):
zitadel, openfga, w2y_module — owner w2y, password from ${POSTGRES_PASSWORD}.
Environment contract (.env.example — keys only, no values):
POSTGRES_PASSWORD=
ZITADEL_MASTERKEY=
ZITADEL_EXTERNALDOMAIN=
ZITADEL_EXTERNALSECURE=
OPENFGA_DATASTORE_URI=
UNKEY_MASTER_KEY=
UNKEY_DATABASE_URI=
ZITADEL must run with its supported Postgres config vars (e.g. ZITADEL_DATABASE_POSTGRES_*),
not a hand-rolled DSN, per upstream docs.
3. File Execution Matrix
| Action | Path |
|---|---|
| Create | infra/identity/compose.yaml |
| Create | infra/identity/.env.example |
| Create | infra/identity/bootstrap/init-databases.sql |
| Create | infra/identity/README.md |
| Modify | .gitignore (add infra/identity/.env) |
4. Verification & Testing Strategy
Run on the target host after docker compose up -d:
cd infra/identity
docker compose config -q # schema valid
docker compose ps # all services Up/healthy
curl -sf http://127.0.0.1:8080/debug/health # ZITADEL -> 200
curl -sf http://127.0.0.1:8081/healthz # OpenFGA -> 200 {"status":"SERVING"}
docker compose exec -T postgres psql -U w2y -l | grep -E 'zitadel|openfga|w2y_module'
Record the exact pinned image tags in the README.
Constraints: ARM64 (Oracle Ampere) — every image must have an arm64 manifest; verify with
docker manifest inspect <image> before pinning. Pin concrete tags (no floating latest in the
delivered file). Never commit secrets.