Skip to content

Commit 74fd85d

Browse files
committed
ci/cd update: all tests in corresponding docker containers
1 parent 6f6b7a8 commit 74fd85d

File tree

4 files changed

+109
-111
lines changed

4 files changed

+109
-111
lines changed

.github/workflows/backend-ci.yml

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,30 @@ jobs:
7676
with:
7777
images: ${{ env.MONGO_IMAGE }} ${{ env.REDIS_IMAGE }} ${{ env.KAFKA_IMAGE }} ${{ env.ZOOKEEPER_IMAGE }} ${{ env.SCHEMA_REGISTRY_IMAGE }}
7878

79-
- name: Set up uv
80-
uses: astral-sh/setup-uv@v7
79+
- name: Set up Docker Buildx
80+
uses: docker/setup-buildx-action@v3
81+
82+
- name: Build base image
83+
uses: docker/build-push-action@v6
8184
with:
82-
enable-cache: true
83-
cache-dependency-glob: "backend/uv.lock"
85+
context: ./backend
86+
file: ./backend/Dockerfile.base
87+
load: true
88+
tags: base:latest
89+
cache-from: type=gha,scope=backend-base
90+
cache-to: type=gha,mode=max,scope=backend-base
8491

85-
- name: Install Python dependencies
86-
run: |
87-
cd backend
88-
uv python install 3.12
89-
uv sync --frozen
92+
- name: Build backend image
93+
run: docker build -t integr8scode-backend:latest -f ./backend/Dockerfile ./backend
9094

9195
- name: Start infrastructure services
9296
run: ./deploy.sh infra --wait
9397

9498
- name: Run integration tests
9599
timeout-minutes: 10
96100
run: |
97-
cd backend
98-
uv run pytest tests/integration -v -rs \
101+
docker compose run --rm -T backend \
102+
uv run pytest tests/integration -v -rs \
99103
--durations=0 \
100104
--cov=app \
101105
--cov-report=xml --cov-report=term
@@ -138,17 +142,21 @@ jobs:
138142
with:
139143
images: ${{ env.MONGO_IMAGE }} ${{ env.REDIS_IMAGE }} ${{ env.KAFKA_IMAGE }} ${{ env.ZOOKEEPER_IMAGE }} ${{ env.SCHEMA_REGISTRY_IMAGE }}
140144

141-
- name: Set up uv
142-
uses: astral-sh/setup-uv@v7
145+
- name: Set up Docker Buildx
146+
uses: docker/setup-buildx-action@v3
147+
148+
- name: Build base image
149+
uses: docker/build-push-action@v6
143150
with:
144-
enable-cache: true
145-
cache-dependency-glob: "backend/uv.lock"
151+
context: ./backend
152+
file: ./backend/Dockerfile.base
153+
load: true
154+
tags: base:latest
155+
cache-from: type=gha,scope=backend-base
156+
cache-to: type=gha,mode=max,scope=backend-base
146157

147-
- name: Install Python dependencies
148-
run: |
149-
cd backend
150-
uv python install 3.12
151-
uv sync --frozen
158+
- name: Build backend image
159+
run: docker build -t integr8scode-backend:latest -f ./backend/Dockerfile ./backend
152160

153161
- name: Start infrastructure services
154162
run: ./deploy.sh infra --wait
@@ -158,19 +166,21 @@ jobs:
158166
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -
159167
mkdir -p /home/runner/.kube
160168
sudo k3s kubectl config view --raw > /home/runner/.kube/config
161-
sudo chmod 600 /home/runner/.kube/config
169+
# Use host IP instead of localhost so containers can reach k3s API
170+
HOST_IP=$(hostname -I | awk '{print $1}')
171+
sed -i "s/127.0.0.1/${HOST_IP}/g" /home/runner/.kube/config
172+
sudo chmod 644 /home/runner/.kube/config
162173
export KUBECONFIG=/home/runner/.kube/config
163174
timeout 90 bash -c 'until sudo k3s kubectl cluster-info; do sleep 5; done'
164175
kubectl create namespace integr8scode --dry-run=client -o yaml | kubectl apply -f -
165176
166177
- name: Run E2E tests
167178
timeout-minutes: 10
168-
env:
169-
KUBECONFIG: /home/runner/.kube/config
170-
K8S_NAMESPACE: integr8scode
171179
run: |
172-
cd backend
173-
uv run pytest tests/e2e -v -rs \
180+
docker compose run --rm -T \
181+
-v /home/runner/.kube/config:/app/kubeconfig.yaml:ro \
182+
backend \
183+
uv run pytest tests/e2e -v -rs \
174184
--durations=0 \
175185
--cov=app \
176186
--cov-report=xml --cov-report=term

.github/workflows/frontend-ci.yml

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,6 @@ jobs:
5454
needs: unit
5555
runs-on: ubuntu-latest
5656

57-
services:
58-
registry:
59-
image: registry:2
60-
ports:
61-
- 5000:5000
62-
6357
env:
6458
MONGO_IMAGE: mongo:8.0
6559
REDIS_IMAGE: redis:7-alpine
@@ -92,8 +86,6 @@ jobs:
9286

9387
- name: Setup Docker Buildx
9488
uses: docker/setup-buildx-action@v3
95-
with:
96-
driver-opts: network=host
9789

9890
- name: Setup Kubernetes (k3s)
9991
run: |
@@ -111,32 +103,18 @@ jobs:
111103
/home/runner/.kube/config > backend/kubeconfig.yaml
112104
chmod 644 backend/kubeconfig.yaml
113105
114-
- name: Build and push base image
106+
- name: Build base image
115107
uses: docker/build-push-action@v6
116108
with:
117109
context: ./backend
118110
file: ./backend/Dockerfile.base
119-
push: true
120-
tags: localhost:5000/integr8scode-base:latest
111+
load: true
112+
tags: base:latest
121113
cache-from: type=gha,scope=backend-base
122114
cache-to: type=gha,mode=max,scope=backend-base
123115

124-
- name: Load base image to Docker daemon
125-
run: |
126-
docker pull localhost:5000/integr8scode-base:latest
127-
docker tag localhost:5000/integr8scode-base:latest integr8scode-base:latest
128-
129116
- name: Build backend image
130-
uses: docker/build-push-action@v6
131-
with:
132-
context: ./backend
133-
file: ./backend/Dockerfile
134-
load: true
135-
tags: integr8scode-backend:latest
136-
build-contexts: |
137-
base=docker-image://localhost:5000/integr8scode-base:latest
138-
cache-from: type=gha,scope=backend
139-
cache-to: type=gha,mode=max,scope=backend
117+
run: docker build -t integr8scode-backend:latest -f ./backend/Dockerfile ./backend
140118

141119
- name: Build cert-generator image
142120
uses: docker/build-push-action@v6
@@ -160,11 +138,11 @@ jobs:
160138

161139
- name: Build worker images
162140
run: |
163-
docker build -t integr8scode-coordinator:latest -f backend/workers/Dockerfile.coordinator --build-context base=docker-image://integr8scode-base:latest backend
164-
docker build -t integr8scode-k8s-worker:latest -f backend/workers/Dockerfile.k8s_worker --build-context base=docker-image://integr8scode-base:latest backend
165-
docker build -t integr8scode-pod-monitor:latest -f backend/workers/Dockerfile.pod_monitor --build-context base=docker-image://integr8scode-base:latest backend
166-
docker build -t integr8scode-result-processor:latest -f backend/workers/Dockerfile.result_processor --build-context base=docker-image://integr8scode-base:latest backend
167-
docker build -t integr8scode-saga-orchestrator:latest -f backend/workers/Dockerfile.saga_orchestrator --build-context base=docker-image://integr8scode-base:latest backend
141+
docker build -t integr8scode-coordinator:latest -f backend/workers/Dockerfile.coordinator --build-context base=docker-image://base:latest ./backend
142+
docker build -t integr8scode-k8s-worker:latest -f backend/workers/Dockerfile.k8s_worker --build-context base=docker-image://base:latest ./backend
143+
docker build -t integr8scode-pod-monitor:latest -f backend/workers/Dockerfile.pod_monitor --build-context base=docker-image://base:latest ./backend
144+
docker build -t integr8scode-result-processor:latest -f backend/workers/Dockerfile.result_processor --build-context base=docker-image://base:latest ./backend
145+
docker build -t integr8scode-saga-orchestrator:latest -f backend/workers/Dockerfile.saga_orchestrator --build-context base=docker-image://base:latest ./backend
168146
169147
- name: Start full stack
170148
run: ./deploy.sh dev --ci

deploy.sh

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -265,27 +265,22 @@ cmd_check() {
265265
cmd_test() {
266266
print_header "Running Test Suite"
267267

268-
print_info "Starting services..."
269-
docker compose up -d --build
268+
print_info "Building images..."
269+
docker build -t base:latest -f ./backend/Dockerfile.base ./backend
270+
docker build -t integr8scode-backend:latest -f ./backend/Dockerfile ./backend
270271

271-
print_info "Waiting for backend to be healthy..."
272-
if ! curl --retry 60 --retry-delay 5 --retry-all-errors -ksfo /dev/null https://localhost:443/api/v1/health/live; then
273-
print_error "Backend failed to become healthy"
274-
docker compose logs
275-
exit 1
276-
fi
277-
print_success "Backend is healthy"
272+
print_info "Starting infrastructure..."
273+
cmd_infra --wait
278274

279-
print_info "Running tests..."
280-
cd backend
281-
if uv run pytest tests/integration tests/unit -v --cov=app --cov-report=term; then
275+
print_info "Running tests inside Docker..."
276+
if docker compose run --rm -T backend \
277+
uv run pytest tests/integration tests/unit -v --cov=app --cov-report=term; then
282278
print_success "All tests passed!"
283279
TEST_RESULT=0
284280
else
285281
print_error "Tests failed"
286282
TEST_RESULT=1
287283
fi
288-
cd ..
289284

290285
print_info "Cleaning up..."
291286
docker compose down

docs/operations/cicd.md

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -125,56 +125,60 @@ After each image builds, [Trivy](https://trivy.dev/) scans it for known vulnerab
125125
dependencies. The scan fails if it finds any critical or high severity issues with available fixes. Results upload to
126126
GitHub Security for tracking. The backend scan respects `.trivyignore` for acknowledged vulnerabilities.
127127

128-
## Integration tests
128+
## Backend tests
129129

130-
The integration test workflow is the most complex. It spins up the entire stack on a GitHub Actions runner to verify
131-
that services work together correctly.
130+
The backend CI workflow runs three test jobs: unit tests (no infrastructure needed), integration tests (requires
131+
infrastructure), and E2E tests (requires infrastructure and Kubernetes).
132+
133+
### Integration tests
134+
135+
Integration tests verify that services work together correctly. Tests run inside Docker containers to ensure the same
136+
environment as production.
132137

133138
```mermaid
134139
sequenceDiagram
135140
participant GHA as GitHub Actions
136-
participant K3s as K3s Cluster
137141
participant Docker as Docker Compose
138-
participant Tests as pytest
139-
140-
GHA->>K3s: Install k3s
141-
GHA->>Docker: Pre-pull base images
142-
GHA->>Docker: Build services (bake)
143-
GHA->>Docker: Start compose stack
144-
Docker->>Docker: Wait for health checks
145-
GHA->>Tests: Run pytest
146-
Tests->>Docker: HTTP requests
147-
Tests-->>GHA: Coverage report
142+
participant Tests as pytest (in container)
143+
144+
GHA->>Docker: Build base + backend images
145+
GHA->>Docker: Start infrastructure (infra --wait)
146+
GHA->>Docker: docker compose run backend pytest
147+
Tests->>Docker: Connect to kafka:29092, mongo:27017
148+
Tests-->>GHA: Coverage report (via volume mount)
148149
GHA->>GHA: Upload to Codecov
149150
```
150151

151-
The workflow starts by installing [k3s](https://k3s.io/), a lightweight Kubernetes distribution, so the backend can
152-
interact with a real cluster during tests. It pre-pulls container images in parallel to avoid cold-start delays during
153-
the build step.
152+
The workflow builds the base image with GHA layer caching using [docker/build-push-action](https://github.com/docker/build-push-action),
153+
then builds the backend image on top. Infrastructure services start via `./deploy.sh infra --wait`.
154154

155-
The CI workflow uses `deploy.sh` to start the infrastructure, ensuring consistency between local development and CI
156-
environments. The `deploy.sh dev --ci` command starts the full stack without observability services (Jaeger, Grafana,
157-
etc.) and waits for all services to be healthy before proceeding. For backend-only tests, `deploy.sh infra --wait`
158-
starts just the infrastructure services (MongoDB, Redis, Kafka, Zookeeper, Schema Registry).
155+
Tests run inside a container using `docker compose run --rm -T backend`, which:
159156

160-
The [docker/bake-action](https://github.com/docker/bake-action) builds all services with GitHub Actions cache support.
161-
It reads cache layers from previous runs and writes new layers back, so unchanged dependencies don't rebuild. The cache
162-
scopes are branch-specific with a fallback to main, meaning feature branches benefit from the main branch cache even on
163-
their first run.
157+
- Uses the same Docker network as infrastructure services
158+
- Connects to services using Docker hostnames (`kafka:29092`, `mongo:27017`)
159+
- Writes coverage reports to the mounted volume for upload
164160

165-
Once images are built, `docker compose up -d` starts the stack. The workflow then uses curl's built-in retry mechanism
166-
to wait for the backend health endpoint:
161+
The `.env.test` file contains Docker-internal hostnames, ensuring tests use the same configuration locally and in CI.
162+
163+
### E2E tests
164+
165+
E2E tests require Kubernetes for code execution. The workflow installs [k3s](https://k3s.io/) on the runner and
166+
configures the kubeconfig to be accessible from inside Docker containers:
167167

168168
```bash
169-
curl --retry 60 --retry-delay 5 --retry-all-errors -ksf https://127.0.0.1:443/api/v1/health/live
169+
# Update kubeconfig to use host IP instead of localhost
170+
HOST_IP=$(hostname -I | awk '{print $1}')
171+
sed -i "s/127.0.0.1/${HOST_IP}/g" /home/runner/.kube/config
170172
```
171173

172-
This approach is cleaner than shell loops and more reliable than Docker Compose's `--wait` flag (which has issues with
173-
init containers that exit after completion). The backend's `depends_on` configuration ensures MongoDB, Redis, Kafka,
174-
and Schema Registry are healthy before backend starts, so waiting for backend health implicitly waits for all
175-
dependencies. Once the health check passes, the workflow runs pytest against the integration and unit test suites with
176-
coverage reporting. Test isolation uses
177-
per-worker database names and schema registry prefixes to avoid conflicts when pytest-xdist runs tests in parallel.
174+
Tests run inside Docker with the kubeconfig mounted:
175+
176+
```bash
177+
docker compose run --rm -T \
178+
-v /home/runner/.kube/config:/app/kubeconfig.yaml:ro \
179+
backend \
180+
uv run pytest tests/e2e -v
181+
```
178182

179183
Coverage reports go to [Codecov](https://codecov.io/) for tracking over time. The workflow always collects container
180184
logs and Kubernetes events as artifacts, which helps debug failures without reproducing them locally.
@@ -209,17 +213,28 @@ uv run mypy .
209213
# Security scan
210214
uv tool run bandit -r . -x tests/ -ll
211215

212-
# Unit tests only (fast)
216+
# Unit tests only (fast, no infrastructure needed)
213217
uv run pytest tests/unit -v
218+
```
214219

215-
# Full integration tests (requires docker compose up)
216-
uv run pytest tests/integration tests/unit -v
220+
For integration and E2E tests, use Docker to match the CI environment:
221+
222+
```bash
223+
# Start infrastructure
224+
./deploy.sh infra --wait
225+
226+
# Run integration tests inside Docker
227+
docker compose run --rm -T backend \
228+
uv run pytest tests/integration -v
229+
230+
# Run E2E tests (requires k8s configured)
231+
docker compose run --rm -T \
232+
-v ~/.kube/config:/app/kubeconfig.yaml:ro \
233+
backend \
234+
uv run pytest tests/e2e -v
217235
```
218236

219-
For the full integration test experience, start the stack with `docker compose up -d`, wait for the backend to be
220-
healthy, then run pytest. Alternatively, use `./deploy.sh test` which handles startup, health checks, testing, and
221-
cleanup automatically. The CI workflow's yq modifications aren't necessary locally since your environment
222-
likely has the expected configuration already.
237+
Alternatively, use `./deploy.sh test` which handles startup, health checks, testing, and cleanup automatically.
223238

224239
## Build optimizations
225240

0 commit comments

Comments
 (0)