2026-10-06_identity-infra-compose_spec-v2
Spec — Wine2You Identity Stack: ZITADEL + OpenFGA compose (task 1, rev 2)
Date: 2026-10-06 (rev 2 — supersedes 2026-10-06_identity-infra-compose_spec.md)
Repo: digid-co/wine2you → infra/identity/
Target host: contabo1 (8 vCPU / 23 GB / 144 GB free, Docker 29.8.2) — the shared-services host (hosting rule). NOT the Oracle dev box.
Revision note (why this changed)
- Unkey is OUT of this compose — moved to Unkey Cloud (free tier; root key in 1P
Digidvault, itemUnkey). Add only env placeholders. Do not deploy any Unkey container. - Target host is contabo1, not Oracle: identity/authorization is a shared service (blast-radius rule); the Oracle free box is reserved for the Wine2You core app.
1. Goal & Boundary
Deliverable: a Docker Compose stack bringing up ZITADEL + OpenFGA with their own Postgres, version-pinned, plus README + bootstrap SQL. No application code.
In scope: infra/identity/compose.yaml (postgres, zitadel, openfga); .env.example; bootstrap/init-databases.sql; README.md; .gitignore additions.
Out of scope: Unkey (Cloud), the user-mgmt module, storefront/Medusa/Meilisearch, Cloudflare Tunnel, TLS, reverse proxy, any ZITADEL/OpenFGA content.
2. Type & API Contracts
| Service | Image (pin concrete tag) | Ports (host→container) | Persistence |
|---|---|---|---|
postgres |
postgres:16-alpine |
127.0.0.1:5432:5432 |
volume w2y_pgdata |
zitadel |
ghcr.io/zitadel/zitadel:<concrete-tag> |
127.0.0.1:8080:8080 |
uses postgres |
openfga |
openfga/openfga:<concrete-tag> |
127.0.0.1:8081:8080 (http), 127.0.0.1:8082:8081 (grpc) |
uses postgres |
- Network: one bridge
w2y-identity. All bindings 127.0.0.1-only (tailnet/SSH only; nothing public). restart: unless-stoppedeverywhere. Healthchecks onpostgres+openfgaminimum.- ZITADEL via its supported
ZITADEL_DATABASE_POSTGRES_*env vars (not a hand-rolled DSN). - Postgres init SQL creates DBs
zitadel+openfga, ownerw2y.
.env.example keys (no values):
POSTGRES_PASSWORD=
ZITADEL_MASTERKEY=
ZITADEL_EXTERNALDOMAIN=
ZITADEL_EXTERNALSECURE=
ZITADEL_DATABASE_POSTGRES_HOST=postgres
ZITADEL_DATABASE_POSTGRES_PORT=5432
ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel
ZITADEL_DATABASE_POSTGRES_USER_USERNAME=w2y
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=
OPENFGA_DATASTORE_ENGINE=postgres
OPENFGA_DATASTORE_URI=
# Unkey is CLOUD (not deployed here):
UNKEY_ROOT_KEY=
UNKEY_API_URL=https://api.unkey.com
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
cd infra/identity
docker compose config -q # schema valid (no running daemon required)
grep -n "127.0.0.1:" compose.yaml # every published port is loopback-bound
Record exact pinned tags + the contabo1 target in the README. Do not start containers.
Prefer multi-arch (linux/arm64 + linux/amd64) tags; if docker manifest inspect is unavailable, say so.
Constraints: pin concrete tags (no floating latest); never commit secrets; no Unkey container.