The per-job DinD approach failed because Forgejo Runner's service container
DNS resolution does not work when the runner itself uses DinD
(container.docker_host: tcp://dind:2375). The job container could not resolve
the 'dind' service hostname, causing docker compose to fail immediately.
New approach:
- Runner now uses container.docker_host: 'automount' which mounts the host
Docker socket into job containers. The runner runs as root (user: 0:0)
to access /var/run/docker.sock.
- E2E job no longer uses a 'dind' service. docker compose runs directly
against the host Docker daemon inside the job container.
- docker-compose.e2e.yml gets a custom 'e2e' bridge network. All E2E
containers (postgres, backend, frontend, playwright) attach only to this
network, isolating them from other host containers (Nextcloud, Jellyfin,
etc.). They can still reach the internet for vehicle lookup and npm.
Tradeoff: job containers can see other containers via docker ps, but they
are on an isolated network. For a single-user home server, this is the
simplest reliable configuration.
Implement per-job Docker-in-Docker (DinD) for E2E tests, giving each
job a completely isolated Docker daemon and network. This prevents
leakage to the host Docker or other containers.
The previous E2E approach failed because:
1. The Forgejo runner's container.docker_host was not set, causing
the runner itself to try unix:///var/run/docker.sock and crash-loop.
2. The host DinD daemon had isolated networking — job containers
running docker compose could not resolve 'dind' hostname or access
host filesystem bind mounts (e.g. .:/app).
New approach — zero bind mounts, all COPY-based images:
- docker/backend.e2e.Dockerfile: multi-stage build from repo root.
Copies gradlew + settings.gradle + backend/build.gradle to download
dependencies in a cacheable layer, then copies backend/src and builds
the bootJar. Runs the JAR directly on startup.
- docker/frontend.e2e.Dockerfile: multi-stage Node build → nginx.
Reuses existing docker/nginx.conf for /api proxy to backend service.
No volume mounts, fully self-contained.
- docker/playwright.e2e.Dockerfile: extends official Playwright image.
Installs deps from package-lock.json, copies e2e tests + config.
- docker-compose.e2e.yml: zero bind mounts. Services depend on each
other in order: postgres (healthy) → backend → frontend → playwright.
Playwright waits for backend and frontend via curl loops before
running tests.
- .forgejo/workflows/ci.yml: E2E job adds a 'dind' service container
(docker:28-dind, privileged, no TLS). The job sets DOCKER_HOST to
tcp://dind:2375 so the docker CLI inside the job talks to the
per-job DinD daemon. The compose file is docker-compose.e2e.yml.
- Runner fix on tocke: added container.docker_host: 'tcp://dind:2375'
to runner-config.yaml so the runner's own Docker client connects to
the host DinD container, stopping the crash loop.
Key properties:
- Network isolation: each E2E job gets its own DinD with its own
container network. No host container visibility.
- No bind mount leakage: all images use COPY instead of volume mounts.
The per-job DinD has its own filesystem and can't see host paths.
- Deterministic: builds start from clean state every time. Image cache
exists only within the per-job DinD lifetime.
- Lint-and-test job is untouched and remains green.
- Backend coverage runs from repo root where gradlew lives
- Frontend coverage runs from frontend/ with working-directory
- No cd tricks that break relative paths
- Remove working-directory: frontend from coverage step
- cd back to repo root for ./gradlew command, then cd frontend for npm
- Gradle wrapper lives at repo root, not in frontend/
- Rename FORGEJO_SERVER_URL to GITHUB_SERVER_URL
- The actions/checkout action reads GITHUB_SERVER_URL to construct the
clone URL. The runner was cloning https://srvr.nu/jocke/bilhej/ instead
of https://srvr.nu/git/jocke/bilhej/ because the /git/ subpath was lost
- Add .forgejo/workflows/ci.yml triggering on push/PR to master and develop
- Job lint-and-test: ESLint, vue-tsc type check, Vitest, JUnit, coverage
- Job e2e: Docker compose CI stack with Postgres, backend, frontend, Playwright
- Backend tests use H2 in-memory, no Postgres needed for unit tests
- E2E reuses existing docker-compose.ci.yml orchestration
- Strep env vars use fake test values since Stripe integration is deferred