dorfteich/.gitea/workflows/cd.yml
Claude Fable 5 6a520e27b1
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m40s
CI / Build container images (pull_request) Successful in 4m15s
CI / Auth e2e pack (pull_request) Successful in 8m33s
CI / Import/export fidelity gate (pull_request) Successful in 59s
#236: pin the Node version
.node-version (22.15.1) becomes the single authoritative Node version:
CI/CD select Node only via node-version-file, every Dockerfile pins
node:22.15.1-alpine, and the engines floor in package.json states the
same version (open-ended upwards so a newer local Node keeps working —
reproducibility rests on images and CI). An early CI step fails on any
drift between those places; update procedure in operations.md
(Update strategy). Precondition for the reproducibility claim in #219.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
2026-07-31 04:14:55 +02:00

144 lines
5.6 KiB
YAML

# CD: every push to main builds images once, deploys them to Test, runs
# the smoke suite against the live Test stage, and promotes the identical
# images to Int on success (ADR 0014). Prod deploys are a separate,
# manually gated release workflow (issue #89).
name: CD
on:
push:
branches: [main]
env:
IMAGE_BASE: gitea.101010.cloud/stwaidele/dorfteich
DEPLOY_HOST: one.101010.cloud
jobs:
build-push:
name: Build and push images
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Log in to the Gitea registry
run: printf '%s' "${{ secrets.REGISTRY_TOKEN }}" | tr -d '[:space:]' | docker login gitea.101010.cloud -u fable-5 --password-stdin
# Tags: the immutable SHA (promotion + rollback target) and the
# moving `test` tag that the Test stage's compose file pulls.
- name: Build and push web image
run: |
docker build -f apps/web/Dockerfile --build-arg APP_VERSION=${{ github.sha }} \
-t $IMAGE_BASE-web:${{ github.sha }} -t $IMAGE_BASE-web:test .
docker push $IMAGE_BASE-web:${{ github.sha }}
docker push $IMAGE_BASE-web:test
- name: Build and push api image
run: |
docker build -f apps/api/Dockerfile --build-arg APP_VERSION=${{ github.sha }} \
-t $IMAGE_BASE-api:${{ github.sha }} -t $IMAGE_BASE-api:test .
docker push $IMAGE_BASE-api:${{ github.sha }}
docker push $IMAGE_BASE-api:test
- name: Build and push collab image
run: |
docker build -f apps/collab/Dockerfile --build-arg APP_VERSION=${{ github.sha }} \
-t $IMAGE_BASE-collab:${{ github.sha }} -t $IMAGE_BASE-collab:test .
docker push $IMAGE_BASE-collab:${{ github.sha }}
docker push $IMAGE_BASE-collab:test
- name: Build and push backup image
run: |
docker build -f apps/backup/Dockerfile --build-arg APP_VERSION=${{ github.sha }} \
-t $IMAGE_BASE-backup:${{ github.sha }} -t $IMAGE_BASE-backup:test .
docker push $IMAGE_BASE-backup:${{ github.sha }}
docker push $IMAGE_BASE-backup:test
deploy-test:
name: Deploy to Test
needs: build-push
runs-on: ubuntu-latest
steps:
- name: Set up SSH
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY_TEST }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
printf '%s\n' "${{ secrets.DEPLOY_HOST_KEY }}" > ~/.ssh/known_hosts
- name: Pull and restart the Test stack
run: |
ssh deploy@$DEPLOY_HOST 'cd /srv/DOCKER/dorfteich-test \
&& docker compose pull --quiet && docker compose up -d --remove-orphans \
&& docker compose ps'
smoke-test:
name: Smoke tests against Test
needs: deploy-test
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browser
run: pnpm --filter @dorfteich/web exec playwright install --with-deps chromium
- name: Wait for the Test stage to be ready
run: |
for i in $(seq 1 30); do
code=$(curl -s -o /dev/null -w '%{http_code}' https://test.dorfteich.cloud/api/v1/readyz || true)
[ "$code" = "200" ] && exit 0
sleep 5
done
echo "Test stage did not become ready" >&2; exit 1
- name: Run smoke suite
run: E2E_BASE_URL=https://test.dorfteich.cloud pnpm --filter @dorfteich/web exec playwright test e2e/smoke.spec.ts
promote-int:
name: Promote to Int
needs: smoke-test
runs-on: ubuntu-latest
# Toggle: set the repo/org Actions variable `RUN_INT_DEPLOY` to `false`
# (Gitea → Settings → Actions → Variables) to skip redeploying the Int
# stack (e.g. host maintenance); unset/anything-else runs the normal
# promotion. Int is a preview stage; CI quality gates are unaffected.
if: ${{ vars.RUN_INT_DEPLOY != 'false' }}
steps:
- name: Log in to the Gitea registry
run: printf '%s' "${{ secrets.REGISTRY_TOKEN }}" | tr -d '[:space:]' | docker login gitea.101010.cloud -u fable-5 --password-stdin
# Registry-side retag of the SHA images that just passed on Test —
# Int always runs bit-identical images, never a rebuild.
- name: Retag SHA images as :int
run: |
docker buildx imagetools create -t $IMAGE_BASE-web:int $IMAGE_BASE-web:${{ github.sha }}
docker buildx imagetools create -t $IMAGE_BASE-api:int $IMAGE_BASE-api:${{ github.sha }}
docker buildx imagetools create -t $IMAGE_BASE-collab:int $IMAGE_BASE-collab:${{ github.sha }}
docker buildx imagetools create -t $IMAGE_BASE-backup:int $IMAGE_BASE-backup:${{ github.sha }}
- name: Set up SSH
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY_INT }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
printf '%s\n' "${{ secrets.DEPLOY_HOST_KEY }}" > ~/.ssh/known_hosts
- name: Pull and restart the Int stack
run: |
ssh deploy@$DEPLOY_HOST 'cd /srv/DOCKER/dorfteich-int \
&& docker compose pull --quiet && docker compose up -d --remove-orphans \
&& docker compose ps'