-
Notifications
You must be signed in to change notification settings - Fork 0
feat(dev): Docker Compose local stack + hash-guarded codegen #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
teetangh
wants to merge
4
commits into
dev
Choose a base branch
from
feat/local-docker-devenv
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
40fe88e
feat(dev): docker compose local stack + hash-guarded codegen
cabd7c6
fix(dev): apply dev seed per prompt block instead of one script
4ee8b03
docs(dev): record host free-space requirement and containerd corrupti…
f656a9f
fix(backend): two bugs found by actually running the container
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Familiarise local development. | ||
| # | ||
| # make up everything (Postgres + API + Flutter web) | ||
| # make db Postgres only — the fast path if containers feel slow | ||
| # make down stop, keeping data and build caches | ||
| # | ||
| # `make help` lists everything. | ||
|
|
||
| .DEFAULT_GOAL := help | ||
| .PHONY: help up db api down reset logs shell psql test regen regen-force seed-sql doctor | ||
|
|
||
| help: ## Show this help | ||
| @grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \ | ||
| | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' | ||
|
|
||
| up: ## Start the full stack (Postgres + API + Flutter web) | ||
| docker compose up | ||
|
|
||
| db: ## Start Postgres only, provisioned — use with a native `dart_frog dev` | ||
| docker compose up db-init | ||
|
|
||
| api: ## Start Postgres + API, no Flutter web | ||
| docker compose up db-init api | ||
|
|
||
| down: ## Stop everything, KEEPING the database and build caches | ||
| docker compose down | ||
|
|
||
| reset: ## Destroy the database and all build caches (next start is a cold rebuild) | ||
| docker compose down -v | ||
|
|
||
| logs: ## Tail logs from all services | ||
| docker compose logs -f | ||
|
|
||
| shell: ## Open a shell in the API container | ||
| docker compose exec api bash | ||
|
|
||
| psql: ## Open psql against the local database | ||
| docker compose exec db psql -U familiarise -d familiarise | ||
|
|
||
| test: ## Run the backend test suite inside the API container | ||
| docker compose exec api dart test | ||
|
|
||
| regen: ## Backend codegen, skipped if schema/versions/build.yaml are unchanged | ||
| ./scripts/regenerate-build.sh --prisma | ||
|
|
||
| regen-force: ## Backend codegen, wiping lib/generated first | ||
| ./scripts/regenerate-build.sh --prisma --force | ||
|
|
||
| seed-sql: ## Rebuild backend/prisma/sql/seed-dev.sql from prompts/testing/unit/*.md | ||
| ./scripts/gen-dev-seed.sh | ||
|
|
||
| doctor: ## Check that the local toolchain can run the stack | ||
| @printf 'docker : '; docker --version 2>/dev/null || echo 'MISSING' | ||
| @printf 'compose : '; docker compose version --short 2>/dev/null || echo 'MISSING' | ||
| @printf 'daemon : '; v=$$(docker info --format '{{.ServerVersion}} ({{.Architecture}})' 2>/dev/null); \ | ||
| [ -n "$$v" ] && [ "$$v" != " ()" ] && echo "$$v" || echo 'NOT RUNNING — start Docker Desktop' | ||
| @printf 'flutter : '; flutter --version 2>/dev/null | head -1 || echo 'MISSING' | ||
| @printf 'port 5433 : '; lsof -ti:5433 >/dev/null 2>&1 && echo 'IN USE — set FAM_PG_PORT' || echo 'free' | ||
| @printf 'port 8080 : '; lsof -ti:8080 >/dev/null 2>&1 && echo 'IN USE' || echo 'free' | ||
| @printf 'port 3000 : '; lsof -ti:3000 >/dev/null 2>&1 && echo 'IN USE' || echo 'free' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
| .dart_tool/ | ||
| .packages | ||
| build/ | ||
| .git/ | ||
| .vscode/ | ||
| .serena/ | ||
| *.md | ||
| test/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Template for backend/.env.supabase — the shared cloud database. | ||
| # | ||
| # Copy to backend/.env.supabase (gitignored) and fill in the real values, then: | ||
| # | ||
| # source scripts/use-db.sh supabase # native dart_frog dev | ||
| # FAM_DIRECT_URL="$DIRECT_URL" docker compose up # containers | ||
| # | ||
| # Local development does NOT need this file. The default everywhere is the | ||
| # docker compose Postgres; this exists only for the times you must reproduce | ||
| # something against real data. | ||
| # | ||
| # ⚠️ This is a shared database. Anything you write is visible to everyone and | ||
| # anything you delete is gone. Prefer `source scripts/use-db.sh local`. | ||
| # backend/prisma/sql/seed-dev.sql and docker/db-init refuse to touch any | ||
| # host that is not local, precisely so this cannot be provisioned by | ||
| # accident. | ||
|
|
||
| # Session-mode pooler (port 5432). The backend uses DIRECT_URL because | ||
| # prisma_flutter_connector relies on prepared statements, which PgBouncer's | ||
| # transaction mode does not support. | ||
| DIRECT_URL=postgresql://USER:PASSWORD@aws-0-REGION.pooler.supabase.com:5432/postgres | ||
|
|
||
| # Transaction-mode pooler (port 6543). Kept for parity with the deployed | ||
| # configuration; the backend prefers DIRECT_URL when both are set. | ||
| DATABASE_URL=postgresql://USER:PASSWORD@aws-0-REGION.pooler.supabase.com:6543/postgres?pgbouncer=true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # Familiarise Mobile API — Production Dockerfile | ||
| # | ||
| # IMPORTANT BUILD NOTES (learned from failed deployments): | ||
| # | ||
| # 1. BASE IMAGE: Must use Flutter image, NOT dart:stable. | ||
| # prisma_flutter_connector depends on the Flutter SDK for dependency | ||
| # resolution. Using dart:stable causes "flutter from sdk doesn't exist" | ||
| # during `dart pub get`. | ||
| # | ||
| # 2. PRISMA CODEGEN: lib/generated/ is gitignored (PR #108), so Railway | ||
| # never receives the Prisma-generated types. The Dockerfile must regenerate | ||
| # them via `dart run prisma_flutter_connector:generate`. | ||
| # | ||
| # 3. BUILD_RUNNER: After Prisma generation, freezed/json_serializable must | ||
| # also run. Without this, the AOT compile fails with "Not a constant | ||
| # expression" and undefined type errors in repositories. | ||
| # | ||
| # 4. PRISMA SCHEMA: backend/prisma/schema.prisma was originally a symlink | ||
| # to ../../familiarise_web/prisma/schema.prisma. Docker cannot follow | ||
| # symlinks that point outside the build context. The symlink was replaced | ||
| # with a real file copy. If the web schema changes, re-copy it: | ||
| # cp ~/Desktop/familiarise_web/prisma/schema.prisma backend/prisma/schema.prisma | ||
| # | ||
| # 5. RUNTIME IMAGE: Cannot use `FROM scratch` with the Flutter build image. | ||
| # The official dart:stable image provides /runtime/ (libc + friends) for | ||
| # scratch-based final stages, but the Flutter image does not. AOT-compiled | ||
| # Dart binaries need libc, so we use debian:bookworm-slim instead. | ||
| # | ||
| # 6. LOCAL TESTING: Always test locally before deploying to Railway: | ||
| # docker build -t familiarise-mobile-api . | ||
| # docker run -p 8080:8080 --env-file .env familiarise-mobile-api | ||
| # curl http://localhost:8080/api/health | ||
| # | ||
| # Build steps mirror: scripts/regenerate-build.sh --backend | ||
| # ───────────────────────────────────────────────────────────── | ||
|
|
||
| # ── Stage 1: Build ────────────────────────────────────────── | ||
| FROM ghcr.io/cirruslabs/flutter:stable AS build | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Install dart_frog_cli for the build step | ||
| RUN dart pub global activate dart_frog_cli | ||
| ENV PATH="/root/.pub-cache/bin:${PATH}" | ||
|
|
||
| # Copy and resolve dependencies first (Docker layer caching) | ||
| COPY pubspec.* ./ | ||
| RUN flutter pub get | ||
|
|
||
| # Copy full source | ||
| COPY . . | ||
| RUN flutter pub get --offline | ||
|
|
||
| # 1. Generate Prisma client (lib/generated/ is gitignored, must regenerate) | ||
| RUN dart run prisma_flutter_connector:generate \ | ||
| --schema prisma/schema.prisma \ | ||
| --output lib/generated \ | ||
| --server | ||
|
|
||
| # 2. Run build_runner for freezed/json codegen on generated models | ||
| RUN dart run build_runner build --delete-conflicting-outputs | ||
|
|
||
| # 3. Generate Dart Frog build output (creates build/bin/server.dart) | ||
| RUN dart_frog build | ||
|
|
||
| # 4. AOT compile the generated server to a native binary | ||
| RUN dart compile exe build/bin/server.dart -o build/bin/server | ||
|
|
||
| # ── Stage 2: Runtime ──────────────────────────────────────── | ||
| # Use debian:bookworm-slim (NOT scratch) — the Flutter build image does not | ||
| # provide /runtime/ like dart:stable does. AOT binaries need libc + friends. | ||
| FROM debian:bookworm-slim | ||
|
|
||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| ca-certificates \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| COPY --from=build /app/build/bin/server /app/bin/server | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| CMD ["/app/bin/server"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Development image for the Dart Frog API — hot reload, used by docker-compose. | ||
| # | ||
| # This is NOT the deployment image. See ./Dockerfile for that; its header | ||
| # comments document the constraints both images share, in particular: | ||
| # | ||
| # The base MUST be a Flutter image, not dart:stable. prisma_flutter_connector | ||
| # declares `flutter: sdk: flutter`, so `pub get` fails on a Dart-only SDK with | ||
| # "flutter from sdk doesn't exist". | ||
| # | ||
| # Deliberately does NO code generation at build time. lib/generated is ~735k | ||
| # lines rebuilt from prisma/schema.prisma; baking it into a layer would mean | ||
| # re-running it on every image rebuild. Instead docker-compose keeps | ||
| # lib/generated and .dart_tool in named volumes and the entrypoint regenerates | ||
| # only when the inputs actually change (see backend/scripts/ensure-generated.sh). | ||
|
|
||
| FROM ghcr.io/cirruslabs/flutter:stable | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # curl: used by the compose healthcheck. | ||
| # postgresql-client: lets you psql the database from inside the container. | ||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| curl \ | ||
| ca-certificates \ | ||
| postgresql-client \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| RUN dart pub global activate dart_frog_cli | ||
| ENV PATH="/root/.pub-cache/bin:${PATH}" | ||
|
|
||
| # Copied to /usr/local/bin rather than left in /app, because compose | ||
| # bind-mounts ./backend over /app and would shadow anything placed there. | ||
| COPY docker/dev-entrypoint.sh /usr/local/bin/dev-entrypoint | ||
| RUN chmod +x /usr/local/bin/dev-entrypoint | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| ENTRYPOINT ["/usr/local/bin/dev-entrypoint"] | ||
|
|
||
| # --hostname 0.0.0.0 is required for the published port to be reachable from | ||
| # the host; dart_frog dev binds localhost otherwise. Verified supported in | ||
| # dart_frog_cli 1.2.14 (it must be a literal IP, not a hostname). | ||
| CMD ["dart_frog", "dev", "--hostname", "0.0.0.0", "--port", "8080"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Backend code-generation scope. | ||
| # | ||
| # ── Why this file exists ───────────────────────────────────────────────────── | ||
| # | ||
| # json_serializable was running as a build phase over every Dart file in this | ||
| # package and producing NOTHING. There is not a single `part '*.g.dart'` | ||
| # directive under backend/lib or backend/routes, and no generated .freezed.dart | ||
| # references _$…FromJson — the Prisma connector emits hand-written | ||
| # fromJson/toJson bodies instead (see lib/generated/models/*.dart). | ||
| # | ||
| # The one .g.dart in the package, lib/generated/schema_registry.g.dart, is | ||
| # emitted by `prisma_flutter_connector:generate`, NOT by build_runner, so | ||
| # disabling json_serializable cannot affect it. | ||
| # | ||
| # Disabling it also removes two more phases for free: json_serializable | ||
| # `applies_builders: [source_gen|combining_builder]`, and combining_builder is | ||
| # `auto_apply: none` (so nothing else pulls it in) and itself applies | ||
| # source_gen|part_cleanup. It additionally drops freezed's | ||
| # `runs_before: [json_serializable]` ordering barrier, allowing more parallelism. | ||
| # | ||
| # If you ever add an @JsonSerializable class to the backend, set `enabled: true` | ||
| # below and delete this comment. | ||
|
|
||
| targets: | ||
| $default: | ||
| builders: | ||
| json_serializable:json_serializable: | ||
| enabled: false | ||
|
|
||
| freezed:freezed: | ||
| enabled: true | ||
| # Every @freezed class in this package is Prisma-generated. Scoping | ||
| # freezed to lib/generated stops it walking routes/ (132 files) and the | ||
| # hand-written parts of lib/ looking for annotations that cannot be | ||
| # there. | ||
| generate_for: | ||
| include: | ||
| - lib/generated/**.dart | ||
| exclude: | ||
| - lib/generated/**.freezed.dart |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #!/usr/bin/env bash | ||
| # Entrypoint for the API dev container. | ||
| # | ||
| # Runs before `dart_frog dev`. Everything here is designed to be a no-op on a | ||
| # warm container so `docker compose restart api` comes back in seconds rather | ||
| # than minutes. | ||
|
|
||
| set -euo pipefail | ||
| cd /app | ||
| export PATH="/root/.pub-cache/bin:$PATH" | ||
|
|
||
| # .dart_tool is a named volume, so it starts empty on a fresh volume even | ||
| # though pubspec.yaml is bind-mounted from the host. Resolve if needed. | ||
| if [ ! -f .dart_tool/package_config.json ]; then | ||
| echo "==> flutter pub get (cold volume)" | ||
| flutter pub get | ||
| fi | ||
|
|
||
| # Hash-guarded: regenerates lib/generated only when schema.prisma, the | ||
| # connector/freezed versions, or build.yaml actually changed. On a warm | ||
| # container this prints "up to date" and exits immediately. | ||
| ./scripts/ensure-generated.sh | ||
|
|
||
| echo "==> starting: $*" | ||
| exec "$@" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Run both API containers as a non-root user. Both images retain the default root user, so a compromise of the Dart Frog process has root privileges inside its container.
backend/Dockerfile#L72-L82: create an unprivileged runtime user, copy the server with appropriate ownership, and switch to it beforeCMD.backend/Dockerfile.dev#L16-L43: create and switch to an unprivileged user while ensuring named-volume initialization remains writable.🧰 Tools
🪛 Checkov (3.3.8)
[low] 1-82: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
[low] 1-82: Ensure that a user for the container has been created
(CKV_DOCKER_3)
🪛 Hadolint (2.14.0)
[warning] 74-74: Pin versions in apt get install. Instead of
apt-get install <package>useapt-get install <package>=<version>(DL3008)
📍 Affects 2 files
backend/Dockerfile#L72-L82(this comment)backend/Dockerfile.dev#L16-L43🤖 Prompt for AI Agents
Source: Linters/SAST tools