trails/.github/workflows/cd-apps.yml
Ullrich Schäfer 6c482238e8
Add Grafana deploy annotations to all CD workflows
Posts an annotation to Grafana after each successful deploy, tagged
with "deploy" and the service name. Annotations appear as vertical
markers on dashboards, making it easy to correlate deploys with
metric changes.

Uses GRAFANA_SERVICE_TOKEN from SOPS secrets — must be added via
sops infrastructure/secrets.infra.env. Gracefully skips if the
token is not configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:44:15 +02:00

122 lines
4.1 KiB
YAML

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
- 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
docker compose --env-file app.env up -d journal planner
# Clean up
docker image prune -af
docker compose ps
# Annotate deploy in Grafana
GRAFANA_TOKEN=$(grep GRAFANA_SERVICE_TOKEN app.env | cut -d= -f2-)
if [ -n "$GRAFANA_TOKEN" ]; then
docker compose exec -T grafana wget -q -O /dev/null \
--header="Authorization: Bearer $GRAFANA_TOKEN" \
--header="Content-Type: application/json" \
--post-data='{"text":"Deploy '${{ github.sha }}'","tags":["deploy","apps"]}' \
http://localhost:3000/api/annotations || true
fi