feat(dev): local docker-compose stack
Adds dev/docker-compose.yml + breakpilot-dev Keycloak realm + make dev-up/down/reset/logs targets so a developer can boot the full dependency stack from this repo.
This commit was merged in pull request #4.
This commit is contained in:
@@ -6,6 +6,7 @@ Generated section is appended on release tag via `git-cliff` (see `.gitea/workfl
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- feat(dev): local docker-compose stack (Keycloak + Postgres + Redis + Mongo + MinIO) with pre-imported breakpilot-dev realm and seed users
|
||||
- feat(iac): scaffold orca-platform — manifests/, overlays/, dns/, scripts/, Makefile (M1.1)
|
||||
-
|
||||
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
# make apply ENV=<env> (M1.2+) push resolved set to Orca controller
|
||||
# make diff ENV=<env> alias for plan
|
||||
# make clean remove .orca-out/
|
||||
#
|
||||
# Local dev stack (dev/docker-compose.yml):
|
||||
# make dev-up bring up Keycloak + Postgres + Redis + Mongo + MinIO
|
||||
# make dev-down stop, keep volumes
|
||||
# make dev-reset stop, wipe volumes, fresh start
|
||||
# make dev-logs tail logs from every service
|
||||
|
||||
.PHONY: help validate plan apply diff clean
|
||||
.PHONY: help validate plan apply diff clean dev-up dev-down dev-reset dev-logs
|
||||
|
||||
ENV ?=
|
||||
ORCA_API_URL ?=
|
||||
COMPOSE = docker compose -f dev/docker-compose.yml
|
||||
|
||||
help:
|
||||
@echo "orca-platform targets:"
|
||||
@@ -18,6 +25,12 @@ help:
|
||||
@echo " make apply ENV=<env> push resolved set (no-op until M1.2)"
|
||||
@echo " make diff ENV=<env> alias for plan"
|
||||
@echo " make clean remove .orca-out/"
|
||||
@echo ""
|
||||
@echo "Local dev stack:"
|
||||
@echo " make dev-up bring up the dev compose stack"
|
||||
@echo " make dev-down stop, keep volumes"
|
||||
@echo " make dev-reset stop, wipe volumes, fresh start"
|
||||
@echo " make dev-logs tail logs"
|
||||
|
||||
validate:
|
||||
@./scripts/validate.sh
|
||||
@@ -33,3 +46,18 @@ apply:
|
||||
clean:
|
||||
@rm -rf .orca-out
|
||||
@echo "removed .orca-out/"
|
||||
|
||||
dev-up:
|
||||
@$(COMPOSE) up -d
|
||||
@echo "Keycloak: http://localhost:8080 (admin / admin-dev-pass on master realm)"
|
||||
@echo " realm 'breakpilot-dev', user test@breakpilot.dev / test"
|
||||
|
||||
dev-down:
|
||||
@$(COMPOSE) down
|
||||
|
||||
dev-reset:
|
||||
@$(COMPOSE) down -v
|
||||
@$(COMPOSE) up -d
|
||||
|
||||
dev-logs:
|
||||
@$(COMPOSE) logs -f
|
||||
|
||||
@@ -42,6 +42,8 @@ The single source of truth for which container runs on which VM in which environ
|
||||
|
||||
## Run locally
|
||||
|
||||
### IaC validation
|
||||
|
||||
```bash
|
||||
make validate # check all manifests parse + have required fields
|
||||
make plan ENV=stage # resolve manifests for stage → .orca-out/stage/
|
||||
@@ -51,6 +53,18 @@ make apply ENV=stage # no-op until M1.2 stands up the Orca controller
|
||||
|
||||
`make validate` runs in CI on every PR.
|
||||
|
||||
### Dev stack
|
||||
|
||||
`platform/orca-platform` doubles as the home for the local-dev compose stack so a developer can clone this repo, run `make dev-up`, and immediately work against a real Keycloak realm + Postgres / Redis / Mongo / MinIO. See [`dev/README.md`](./dev/README.md) for the full picture.
|
||||
|
||||
```bash
|
||||
make dev-up # Keycloak (:8080) + Postgres (:5432) + Redis (:6379) + Mongo (:27017) + MinIO (:9000)
|
||||
make dev-down # stop, keep volumes
|
||||
make dev-reset # stop, wipe, fresh
|
||||
```
|
||||
|
||||
Seed user: `test@breakpilot.dev` / `test` (tenant `acme`, products `certifai` + `compliance`).
|
||||
|
||||
## Per-milestone fill-in schedule
|
||||
|
||||
Each stub manifest in `manifests/` carries a header comment naming the milestone that finalises its real values. Summary:
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Local dev stack
|
||||
|
||||
Docker-compose that brings up just enough infrastructure to run `platform/tenant-registry` and `platform/portal` locally with a real Keycloak realm + seed user.
|
||||
|
||||
## What's running
|
||||
|
||||
| Service | Port | Purpose |
|
||||
|---|---|---|
|
||||
| Keycloak 26 | `:8080` | OIDC provider for portal + tenant-registry |
|
||||
| pg-keycloak | (internal only) | Keycloak's backing Postgres |
|
||||
| pg-app | `:5432` | Tenant Registry's Postgres (`platform/platform/platform-dev-pass`) |
|
||||
| Redis | `:6379` | Session cache for portal |
|
||||
| Mongo | `:27017` | (Future) CERTifAI data store |
|
||||
| MinIO | `:9000` / `:9001` console | (Future) Compliance evidence object store |
|
||||
|
||||
Stack is loopback only — no public exposure, no DNS, no TLS. Tenant-registry and portal run on the **host**, not in this compose.
|
||||
|
||||
## Run
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
make dev-up # bring everything up; first start takes ~30s for Keycloak realm import
|
||||
make dev-down # stop, keep volumes
|
||||
make dev-reset # stop, wipe volumes, fresh start
|
||||
make dev-logs # tail logs from every service
|
||||
```
|
||||
|
||||
## First login
|
||||
|
||||
The realm `breakpilot-dev` ships pre-imported with two users:
|
||||
|
||||
| Email | Password | Role |
|
||||
|---|---|---|
|
||||
| `test@breakpilot.dev` | `test` | IT_ADMIN of tenant `acme`, products: certifai + compliance |
|
||||
| `admin@breakpilot.dev`| `admin` | BREAKPILOT_ADMIN (platform staff, backstage access) |
|
||||
|
||||
Verify Keycloak is up: <http://localhost:8080> → click "Administration Console" → log in as `admin`/`admin-dev-pass` (master admin) → switch realm to `breakpilot-dev`.
|
||||
|
||||
## Realm contents
|
||||
|
||||
- **Clients:**
|
||||
- `dev-portal` — public PKCE client used by `platform/portal`. Redirect URIs cover `http://localhost:3000/*` and `http://*.localhost:3000/*` so subdomain routing works in dev.
|
||||
- `dev-tenant-registry` — bearer-only client used by the Go service to validate JWTs.
|
||||
- **Protocol mappers on `dev-portal`** put `tenant_id`, `tenant_slug`, `org_roles`, `products`, `plan`, and `tenant_status` claims into every issued token. The portal middleware reads these to resolve the tenant + render the dashboard. The `test@breakpilot.dev` user has these claims set as user attributes — edit the user in Keycloak admin to flip flags during dev.
|
||||
- **Realm roles** (platform-staff): `BREAKPILOT_ADMIN`, `SUPPORT_ENGINEER`, `SALES_REP`.
|
||||
|
||||
## Reset just Keycloak
|
||||
|
||||
If you mess up the realm and want to reimport the JSON:
|
||||
|
||||
```bash
|
||||
docker compose -f dev/docker-compose.yml down keycloak pg-keycloak
|
||||
docker volume rm breakpilot-dev_pg-keycloak-data
|
||||
make dev-up
|
||||
```
|
||||
|
||||
The other services' data survives.
|
||||
|
||||
## Why this is in `orca-platform`
|
||||
|
||||
This is the only repo that already knows the whole topology — manifests in `manifests/` reference the same images this compose runs. Keeping the dev stack here means a developer clones `orca-platform`, runs `make dev-up`, and is ready to clone the service repo they actually want to work on. Per `INFRASTRUCTURE.md §1` dev runs entirely on developer laptops via docker-compose; this is that compose file.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Local-dev stack for the Breakpilot Platform.
|
||||
#
|
||||
# Brings up the dependencies a developer needs to run tenant-registry +
|
||||
# portal against a real Keycloak realm with seed data — no SysEleven VMs,
|
||||
# no PowerDNS, no Stalwart. Loopback only.
|
||||
#
|
||||
# make dev-up bring this stack up
|
||||
# make dev-down stop it, keep volumes
|
||||
# make dev-reset stop, wipe volumes, bring up fresh
|
||||
#
|
||||
# Networking: every service joins the `breakpilot-dev` bridge network so
|
||||
# tenant-registry and portal (running on the host, not in this compose)
|
||||
# can reach Postgres etc. via `localhost:<port>`. Inter-service traffic
|
||||
# inside the compose (Keycloak → pg-keycloak) uses the service name.
|
||||
|
||||
name: breakpilot-dev
|
||||
|
||||
networks:
|
||||
breakpilot-dev:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
pg-keycloak-data:
|
||||
pg-app-data:
|
||||
mongo-data:
|
||||
minio-data:
|
||||
|
||||
services:
|
||||
# ─── Identity ──────────────────────────────────────────────────────────
|
||||
pg-keycloak:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
networks: [breakpilot-dev]
|
||||
environment:
|
||||
POSTGRES_DB: keycloak
|
||||
POSTGRES_USER: keycloak
|
||||
POSTGRES_PASSWORD: keycloak-dev-pass
|
||||
volumes:
|
||||
- pg-keycloak-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:26.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
pg-keycloak:
|
||||
condition: service_healthy
|
||||
networks: [breakpilot-dev]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
KC_DB: postgres
|
||||
KC_DB_URL: jdbc:postgresql://pg-keycloak:5432/keycloak
|
||||
KC_DB_USERNAME: keycloak
|
||||
KC_DB_PASSWORD: keycloak-dev-pass
|
||||
KC_HEALTH_ENABLED: "true"
|
||||
KC_HOSTNAME: localhost
|
||||
KC_HTTP_ENABLED: "true"
|
||||
KC_PROXY_HEADERS: xforwarded
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: admin-dev-pass
|
||||
command:
|
||||
- start-dev
|
||||
- --import-realm
|
||||
volumes:
|
||||
- ./keycloak/realm-export.json:/opt/keycloak/data/import/breakpilot-dev.json:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && grep -q UP <&3"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 30s
|
||||
|
||||
# ─── App data ──────────────────────────────────────────────────────────
|
||||
pg-app:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
networks: [breakpilot-dev]
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: platform
|
||||
POSTGRES_USER: platform
|
||||
POSTGRES_PASSWORD: platform-dev-pass
|
||||
volumes:
|
||||
- pg-app-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U platform -d platform"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
# ─── Caches + object stores ────────────────────────────────────────────
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
networks: [breakpilot-dev]
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
mongo:
|
||||
image: mongo:7
|
||||
restart: unless-stopped
|
||||
networks: [breakpilot-dev]
|
||||
ports:
|
||||
- "27017:27017"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: mongo-dev-pass
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "echo 'db.runCommand({ping:1})' | mongosh --quiet"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 6
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
restart: unless-stopped
|
||||
networks: [breakpilot-dev]
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: minio-dev
|
||||
MINIO_ROOT_PASSWORD: minio-dev-pass
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9000/minio/health/live"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"realm": "breakpilot-dev",
|
||||
"enabled": true,
|
||||
"displayName": "Breakpilot (dev)",
|
||||
"registrationAllowed": false,
|
||||
"loginWithEmailAllowed": true,
|
||||
"duplicateEmailsAllowed": false,
|
||||
"resetPasswordAllowed": true,
|
||||
"editUsernameAllowed": false,
|
||||
"bruteForceProtected": true,
|
||||
"accessTokenLifespan": 900,
|
||||
"ssoSessionIdleTimeout": 3600,
|
||||
"ssoSessionMaxLifespan": 36000,
|
||||
"internationalizationEnabled": false,
|
||||
"defaultSignatureAlgorithm": "RS256",
|
||||
"organizationsEnabled": true,
|
||||
|
||||
"roles": {
|
||||
"realm": [
|
||||
{ "name": "BREAKPILOT_ADMIN", "description": "Platform staff: full backstage access" },
|
||||
{ "name": "SUPPORT_ENGINEER", "description": "Platform staff: tenant impersonation + read" },
|
||||
{ "name": "SALES_REP", "description": "Platform staff: demo-tenant access only" }
|
||||
]
|
||||
},
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"name": "IT_ADMIN",
|
||||
"path": "/IT_ADMIN",
|
||||
"attributes": { "org_role": ["IT_ADMIN"] }
|
||||
},
|
||||
{
|
||||
"name": "CXO",
|
||||
"path": "/CXO",
|
||||
"attributes": { "org_role": ["CXO"] }
|
||||
},
|
||||
{
|
||||
"name": "USER",
|
||||
"path": "/USER",
|
||||
"attributes": { "org_role": ["USER"] }
|
||||
}
|
||||
],
|
||||
|
||||
"users": [
|
||||
{
|
||||
"username": "test@breakpilot.dev",
|
||||
"email": "test@breakpilot.dev",
|
||||
"emailVerified": true,
|
||||
"firstName": "Test",
|
||||
"lastName": "Acme",
|
||||
"enabled": true,
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "test",
|
||||
"temporary": false
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"tenant_id": ["00000000-0000-0000-0000-000000000001"],
|
||||
"tenant_slug": ["acme"],
|
||||
"org_roles": ["IT_ADMIN"],
|
||||
"products": ["certifai", "compliance"],
|
||||
"plan": ["professional"],
|
||||
"tenant_status": ["active"]
|
||||
},
|
||||
"groups": ["/IT_ADMIN"]
|
||||
},
|
||||
{
|
||||
"username": "admin@breakpilot.dev",
|
||||
"email": "admin@breakpilot.dev",
|
||||
"emailVerified": true,
|
||||
"firstName": "Platform",
|
||||
"lastName": "Admin",
|
||||
"enabled": true,
|
||||
"credentials": [
|
||||
{
|
||||
"type": "password",
|
||||
"value": "admin",
|
||||
"temporary": false
|
||||
}
|
||||
],
|
||||
"realmRoles": ["BREAKPILOT_ADMIN"]
|
||||
}
|
||||
],
|
||||
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "dev-portal",
|
||||
"name": "Customer Portal (dev)",
|
||||
"enabled": true,
|
||||
"publicClient": true,
|
||||
"standardFlowEnabled": true,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"implicitFlowEnabled": false,
|
||||
"serviceAccountsEnabled": false,
|
||||
"rootUrl": "http://localhost:3000",
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"redirectUris": [
|
||||
"http://localhost:3000/*",
|
||||
"http://acme.localhost:3000/*",
|
||||
"http://demo.localhost:3000/*"
|
||||
],
|
||||
"webOrigins": ["+"],
|
||||
"attributes": {
|
||||
"pkce.code.challenge.method": "S256",
|
||||
"post.logout.redirect.uris": "http://localhost:3000/*##http://*.localhost:3000/*"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "tenant_id-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_id",
|
||||
"claim.name": "tenant_id",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tenant_slug-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_slug",
|
||||
"claim.name": "tenant_slug",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "org_roles-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "org_roles",
|
||||
"claim.name": "org_roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "products-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "products",
|
||||
"claim.name": "products",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "plan-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "plan",
|
||||
"claim.name": "plan",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tenant_status-mapper",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"config": {
|
||||
"user.attribute": "tenant_status",
|
||||
"claim.name": "tenant_status",
|
||||
"jsonType.label": "String",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "dev-tenant-registry",
|
||||
"name": "Tenant Registry (dev)",
|
||||
"enabled": true,
|
||||
"bearerOnly": true,
|
||||
"publicClient": false,
|
||||
"standardFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user