From ed1bb4084a6d6231160712ce0c62aefb2cb29ea4 Mon Sep 17 00:00:00 2001 From: senke Date: Sun, 26 Apr 2026 10:01:28 +0200 Subject: [PATCH] ci(e2e): replace docker-compose with native services block Symptom: e2e.yml was bringing up Postgres/Redis/RabbitMQ via `docker compose up -d`, which forces the runner job container to share the host docker socket, parses the entire docker-compose.yml at every run (so unrelated interpolations like `${JWT_SECRET:?required}` block the step), and never auto-cleans the started containers. Concurrent e2e runs collided on host ports 15432/16379/15672. Combined with the already-fragile DinD setup, this is one of the top sources of flakes. Fix: use the GHA-native `services:` block. act_runner spawns the three service containers on the job network with healthchecks, exposes them by service hostname on standard ports, tears them down at the end. Net removal: docker-compose dependency, host port mapping, manual readiness loop, leaked-container risk. Wire-shape changes (DB/cache/MQ URLs hoisted to job-level env): postgres -> postgres:5432 (was localhost:15432) redis -> redis:6379 (was localhost:16379, + auth required) rabbitmq -> rabbitmq:5672 (was localhost:5672) REDIS_URL now carries the requirepass secret to match docker-compose.yml's REM-023 convention; previously the runner-side redis happened to start without auth. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/e2e.yml | 76 ++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b67fb0f6b..f12d14d7a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -21,11 +21,8 @@ env: # Forces playwright.config.ts:141,155 to spawn fresh backend + Vite # instead of reusing whatever is on the runner. CI: "true" - # docker-compose.yml has `${JWT_SECRET:?required}` interpolation on - # the backend-api service. Compose validates the WHOLE file even when - # we `up -d` only Postgres / Redis / RabbitMQ — so JWT_SECRET must be - # in env at every step that runs `docker compose ...`. # Falls back to a CI-only dev key if the Forgejo secret is unset. + # Used at the "Build + start backend API" step. JWT_SECRET: ${{ secrets.E2E_JWT_SECRET || 'ci-dev-jwt-secret-32-chars-min-padding!!' }} jobs: @@ -38,6 +35,56 @@ jobs: name: e2e (${{ github.event_name == 'pull_request' && '@critical' || 'full' }}) runs-on: ubuntu-latest timeout-minutes: ${{ github.event_name == 'pull_request' && 20 || 45 }} + + # Service containers are managed by act_runner: spawned on the job + # network with healthchecks, torn down at the end. This replaces + # the previous `docker compose up -d` pattern which relied on + # docker socket sharing + host port mappings — fragile (port + # collisions across concurrent jobs, manual cleanup, double-DinD, + # whole compose file validated even when only 3 services are + # needed). Service hostnames (`postgres`, `redis`, `rabbitmq`) + # resolve from the job container on standard ports. + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: veza + POSTGRES_PASSWORD: devpassword + POSTGRES_DB: veza + options: >- + --health-cmd "pg_isready -U veza" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + redis: + # Match docker-compose.yml (REM-023: password required even + # in dev). Default redis:7-alpine entrypoint reads + # REDIS_ARGS, so requirepass works without a `command:`. + image: redis:7-alpine + env: + REDIS_ARGS: "--requirepass devpassword" + options: >- + --health-cmd "redis-cli -a devpassword ping" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + rabbitmq: + image: rabbitmq:3-management-alpine + env: + RABBITMQ_DEFAULT_USER: veza + RABBITMQ_DEFAULT_PASS: devpassword + options: >- + --health-cmd "rabbitmq-diagnostics -q check_port_connectivity" + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + # Service hostnames + standard ports — no host-port mapping needed. + env: + DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@postgres:5432/veza?sslmode=disable + REDIS_URL: redis://:devpassword@redis:6379 + RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL || 'amqp://veza:devpassword@rabbitmq:5672/' }} + steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -69,29 +116,12 @@ jobs: ./scripts/generate-jwt-keys.sh ./scripts/generate-ssl-cert.sh - - name: Start backend services (Postgres, Redis, RabbitMQ) - run: | - docker compose up -d postgres redis rabbitmq - echo "Waiting for Postgres..." - for i in $(seq 1 30); do - if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then - echo "Postgres ready" - break - fi - sleep 2 - done - docker compose ps - - name: Run database migrations - env: - DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable run: | cd veza-backend-api go run cmd/migrate_tool/main.go - name: Seed database (CI mode — 5 test accounts + minimal fixtures) - env: - DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable run: | cd veza-backend-api go run ./cmd/tools/seed --ci @@ -100,12 +130,8 @@ jobs: env: APP_ENV: test APP_PORT: "18080" - DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable - REDIS_URL: redis://localhost:16379 - # JWT_SECRET inherits from workflow-level env (above). COOKIE_SECURE: "false" CORS_ALLOWED_ORIGINS: http://veza.fr:5174,http://localhost:5174 - RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL || 'amqp://guest:guest@localhost:5672/' }} DISABLE_RATE_LIMIT_FOR_TESTS: "true" RATE_LIMIT_LIMIT: "10000" RATE_LIMIT_WINDOW: "60"