Scaffold FastAPI + MySQL + Keycloak service with devcontainer
Sets up the project skeleton: - FastAPI app factory with lifespan, request-id middleware, and RFC 9457 problem+json error handlers - Async SQLAlchemy 2.0 over MySQL (asyncmy), with a constraint naming convention in place before the first migration and async Alembic - Keycloak as a pure resource server: OIDC discovery, cached JWKS with rotation-aware refresh, and require_roles dependencies - Devcontainer running MySQL 8.4 and Keycloak 26.7 as compose siblings, with the realm (clients, roles, test users) imported on first boot - Test suite covering the endpoints plus the token validator itself, exercised against a locally generated RSA keypair - uv packaging, ruff, mypy --strict, pre-commit, Gitea CI, prod Dockerfile Two Keycloak-in-containers traps are handled explicitly and documented in the README: the issuer/internal-URL split (the browser sees localhost:8080, the API sees keycloak:8080) and the audience mapper that stops Keycloak issuing tokens with aud=account. The devices resource is a placeholder proving the routing -> auth -> ORM -> migration path end to end; replace it with the real domain. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
commit
0526d34e42
52 files changed
+4433
No files matched your search
@@ -0,0 +1,75 @@
|
||||
# Backing services for v2x-server.
|
||||
#
|
||||
# Used two ways:
|
||||
# * on its own -> `docker compose up -d` for a local stack
|
||||
# * with .devcontainer/compose.override.yaml -> adds the "app" dev container
|
||||
#
|
||||
# Credentials here are development-only and intentionally committed.
|
||||
|
||||
name: v2x-server
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.4
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_0900_ai_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
MYSQL_DATABASE: v2x
|
||||
MYSQL_USER: v2x
|
||||
MYSQL_PASSWORD: v2xpassword
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
# Init scripts run only on the first boot of an empty data volume.
|
||||
- ./docker/mysql/init:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-prootpassword"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 30s
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:26.7.0
|
||||
command: ["start-dev", "--import-realm"]
|
||||
environment:
|
||||
# Keycloak 26 renamed these from KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD.
|
||||
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||
# Pin the public identity so every issued token carries
|
||||
# iss=http://localhost:8080/realms/v2x, regardless of which network path
|
||||
# produced it. See README "The two URL traps".
|
||||
KC_HOSTNAME: http://localhost:8080
|
||||
KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
|
||||
KC_HTTP_ENABLED: "true"
|
||||
KC_HEALTH_ENABLED: "true"
|
||||
# Dev mode keeps the realm in an embedded H2 file inside this volume --
|
||||
# deliberately not MySQL, so wiping app data never destroys identity data.
|
||||
KC_DB: dev-file
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- keycloak-data:/opt/keycloak/data
|
||||
- ./docker/keycloak:/opt/keycloak/data/import:ro
|
||||
healthcheck:
|
||||
# The image ships no curl or wget, so probe the management port through
|
||||
# bash's /dev/tcp. Informational only -- nothing blocks on it, because a
|
||||
# failed probe here should not stop you from opening the devcontainer.
|
||||
test:
|
||||
- "CMD-SHELL"
|
||||
- >-
|
||||
exec 3<>/dev/tcp/127.0.0.1/9000 &&
|
||||
printf 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 &&
|
||||
cat <&3 | grep -q 'UP'
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
keycloak-data:
|
||||
Reference in new issue
Block a user