# ╔══════════════════════════════════════════════════════════════════════════╗ # ║ VibeFinance CI/CD — 2-container opzet ║ # ║ ║ # ║ Eén app-image (Express API + gebouwde Vite-frontend) + postgres. ║ # ║ ║ # ║ Stages: ║ # ║ Test backend-tests (Dockerfile target 'test') ║ # ║ Build productie-image bouwen + smoketest op de echte stack ║ # ║ Publish multi-arch image (amd64 + arm64) naar de registry pushen ║ # ║ ║ # ║ Vereist in Azure DevOps: ║ # ║ - Service connection (type: Docker Registry) naar de registry; vul ║ # ║ de naam in bij variabele dockerRegistryServiceConnection ║ # ║ - Agent met Docker en buildx (ubuntu-latest heeft dit) ║ # ╚══════════════════════════════════════════════════════════════════════════╝ trigger: branches: include: - main - master - feature/* tags: include: - "*" pr: branches: include: - main - master variables: # ── Aanpassen per omgeving ────────────────────────────────────────────── dockerRegistryServiceConnection: "vibefinance-registry" registryHost: "vibehoogie.duckdns.org/vibe" imageName: "vibefinance-app" # ── Afgeleid ──────────────────────────────────────────────────────────── imageRepo: "$(registryHost)/$(imageName)" pool: vmImage: ubuntu-latest stages: # ── Stage 1: tests ────────────────────────────────────────────────────── - stage: Test displayName: Tests jobs: - job: BackendTests displayName: Backend-tests (node --test) steps: - checkout: self fetchDepth: 1 - script: | set -euo pipefail docker build --target test -t vibefinance/app:test . docker run --rm vibefinance/app:test displayName: npm test (in Docker, zonder database) # ── Stage 2: image bouwen en smoketesten ──────────────────────────────── - stage: Build displayName: Build en smoketest dependsOn: Test jobs: - job: BuildImage displayName: Productie-image + smoketest steps: - checkout: self fetchDepth: 1 - script: | set -euo pipefail APP_VERSION=$(node -p "require('./package.json').version") echo "##vso[task.setvariable variable=appVersion]$APP_VERSION" echo "Versie: $APP_VERSION" displayName: Versienummer uit package.json - script: | set -euo pipefail docker build --target production \ --build-arg APP_VERSION=$(appVersion) \ --build-arg VITE_APP_VERSION=$(appVersion) \ --build-arg VITE_API_URL=/api \ -t vibefinance/app:ci . displayName: Productie-image bouwen (incl. Vite-build) - script: | set -euo pipefail docker network create vibefinance-ci docker run -d --name ci-postgres --network vibefinance-ci \ -e POSTGRES_DB=vibefinance \ -e POSTGRES_USER=vibefinance \ -e POSTGRES_PASSWORD=cipassword \ postgres:18-alpine for i in $(seq 1 30); do if docker exec ci-postgres pg_isready -U vibefinance >/dev/null 2>&1; then break; fi sleep 2 done docker run -d --name ci-app --network vibefinance-ci \ -e NODE_ENV=production \ -e JWT_SECRET=ci-secret \ -e UPDATE_CHECK_URL= \ -e DATABASE_URL=postgresql://vibefinance:cipassword@ci-postgres:5432/vibefinance \ -p 3001:3001 \ vibefinance/app:ci gezond=0 for i in $(seq 1 30); do if curl -fsS http://localhost:3001/api/health | grep -q ok; then gezond=1; break; fi sleep 2 done if [ "$gezond" -ne 1 ]; then echo "App werd niet gezond. Logs:" docker logs ci-app exit 1 fi echo "/api/health : ok" curl -fsS http://localhost:3001/ | grep -q root echo "/ : index.html" curl -fsS http://localhost:3001/voortgang | grep -q root echo "/voortgang : index.html (SPA-fallback)" code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/api/bestaat-niet) test "$code" = "404" echo "/api/... : 404 JSON" displayName: Smoketest op app + postgres - script: | docker logs ci-app || true docker rm -f ci-app ci-postgres || true docker network rm vibefinance-ci || true displayName: Opruimen condition: always() # ── Stage 3: publiceren ───────────────────────────────────────────────── # Alleen vanaf main/master of een tag; feature-branches en PRs stoppen na Build. - stage: Publish displayName: Publiceren naar registry dependsOn: Build condition: | and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(variables['Build.SourceBranch'], 'refs/heads/master'), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))) jobs: - job: PushImage displayName: Multi-arch image pushen steps: - checkout: self fetchDepth: 1 - script: | set -euo pipefail APP_VERSION=$(node -p "require('./package.json').version") echo "##vso[task.setvariable variable=appVersion]$APP_VERSION" displayName: Versienummer bepalen - task: Docker@2 displayName: Inloggen op de registry inputs: command: login containerRegistry: $(dockerRegistryServiceConnection) - script: | set -euo pipefail docker run --privileged --rm tonistiigi/binfmt --install arm64 docker buildx create --name vibefinance --use docker buildx inspect --bootstrap displayName: buildx voor arm64 (Raspberry Pi) klaarzetten - script: | set -euo pipefail docker buildx build \ --platform linux/amd64,linux/arm64 \ --target production \ --build-arg APP_VERSION=$(appVersion) \ --build-arg VITE_APP_VERSION=$(appVersion) \ --build-arg VITE_API_URL=/api \ -t $(imageRepo):$(appVersion) \ -t $(imageRepo):latest \ --push . displayName: Bouwen en pushen (amd64 + arm64) - script: | echo "Gepubliceerd:" echo " $(imageRepo):$(appVersion)" echo " $(imageRepo):latest" displayName: Samenvatting