name: CD Apps on: push: branches: [main] paths: - "apps/**" - "packages/**" - "pnpm-lock.yaml" workflow_dispatch: {} concurrency: group: deploy-apps cancel-in-progress: true jobs: build-images: name: Build & Push Docker Images runs-on: ubuntu-latest environment: production permissions: contents: read packages: write strategy: matrix: app: [journal, planner] steps: - uses: actions/checkout@v6 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Decrypt Sentry auth token run: | curl -sLO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64 chmod +x sops-v3.9.4.linux.amd64 SOPS_AGE_KEY="${{ secrets.AGE_SECRET_KEY }}" ./sops-v3.9.4.linux.amd64 -d infrastructure/secrets.app.env > /tmp/secrets.env grep SENTRY_AUTH_TOKEN /tmp/secrets.env | cut -d= -f2- | tr -d '\n' > /tmp/sentry_token - uses: docker/build-push-action@v7 with: context: . file: apps/${{ matrix.app }}/Dockerfile push: true tags: | ghcr.io/trails-cool/${{ matrix.app }}:latest ghcr.io/trails-cool/${{ matrix.app }}:${{ github.sha }} build-args: | SENTRY_RELEASE=${{ github.sha }} secrets: | SENTRY_AUTH_TOKEN=/tmp/sentry_token deploy: name: Deploy Apps needs: [build-images] runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v6 - name: Decrypt secrets run: | curl -sLO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64 chmod +x sops-v3.9.4.linux.amd64 SOPS_AGE_KEY="${{ secrets.AGE_SECRET_KEY }}" ./sops-v3.9.4.linux.amd64 -d infrastructure/secrets.app.env > infrastructure/app.env echo "SENTRY_RELEASE=${{ github.sha }}" >> infrastructure/app.env echo "DOMAIN=trails.cool" >> infrastructure/app.env # Flagship marker — see cd-infra.yml for what this gates. echo "IS_FLAGSHIP=true" >> infrastructure/app.env - name: Copy files to server uses: appleboy/scp-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: root key: ${{ secrets.DEPLOY_SSH_KEY }} source: "infrastructure/docker-compose.yml,infrastructure/Caddyfile" target: /opt/trails-cool strip_components: 1 - name: Copy secrets to server uses: appleboy/scp-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: root key: ${{ secrets.DEPLOY_SSH_KEY }} source: "infrastructure/app.env" target: /opt/trails-cool strip_components: 1 - name: Deploy via SSH uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: root key: ${{ secrets.DEPLOY_SSH_KEY }} script: | cd /opt/trails-cool # Login to ghcr.io GHCR_TOKEN=$(grep DEPLOY_GHCR_TOKEN app.env | cut -d= -f2-) echo "$GHCR_TOKEN" | docker login ghcr.io -u stigi --password-stdin # Pull and deploy app containers docker compose --env-file app.env pull journal planner docker compose --env-file app.env run --rm journal npx drizzle-kit push --config /app/packages/db/drizzle.config.ts --force # --remove-orphans cleans up containers whose service was deleted # from the compose file, matching cd-infra's behaviour. docker compose --env-file app.env up -d --remove-orphans journal planner # Clean up docker image prune -af docker compose ps # Annotate deploy in Grafana GRAFANA_TOKEN=$(grep GRAFANA_SERVICE_TOKEN .env | cut -d= -f2- 2>/dev/null) if [ -n "$GRAFANA_TOKEN" ]; then docker compose exec -T grafana curl -sf -X POST \ -H "Authorization: Bearer $GRAFANA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Deploy ${{ github.sha }}","tags":["deploy","apps"]}' \ http://localhost:3000/api/annotations || true fi