Healthchecks prove containers are healthy on the docker network; they do not prove the public URL is reachable, HSTS still fires, or /actuator is still blocked at the edge. Add a post-deploy smoke step to nightly.yml that: 1. GETs https://staging.raddatz.cloud/login (frontend reachable) 2. asserts the response includes the Strict-Transport-Security header 3. asserts /actuator/health returns 404 (defense-in-depth verified) Failure aborts the workflow before the env-file cleanup step. The cleanup step still runs because it is `if: always()`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
3.1 KiB
YAML
95 lines
3.1 KiB
YAML
name: nightly
|
|
|
|
# Builds and deploys the staging environment from main every night.
|
|
# Runs on the self-hosted runner using Docker-out-of-Docker (the docker
|
|
# socket is mounted in), so `docker compose build` produces images on
|
|
# the host daemon and `docker compose up` consumes them directly — no
|
|
# registry hop.
|
|
#
|
|
# Staging environment isolation:
|
|
# - project name: archiv-staging
|
|
# - host ports: backend 8081, frontend 3001
|
|
# - profile: staging (starts mailpit instead of a real SMTP relay)
|
|
#
|
|
# Required Gitea secrets:
|
|
# STAGING_POSTGRES_PASSWORD
|
|
# STAGING_MINIO_PASSWORD
|
|
# STAGING_MINIO_APP_PASSWORD
|
|
# STAGING_OCR_TRAINING_TOKEN
|
|
# STAGING_APP_ADMIN_USERNAME
|
|
# STAGING_APP_ADMIN_PASSWORD
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "0 2 * * *"
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
# Ensures the backend Dockerfile's `RUN --mount=type=cache` lines are
|
|
# honoured (Maven cache survives between runs).
|
|
DOCKER_BUILDKIT: "1"
|
|
|
|
jobs:
|
|
deploy-staging:
|
|
runs-on: self-hosted
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Write staging env file
|
|
run: |
|
|
cat > .env.staging <<EOF
|
|
TAG=nightly
|
|
PORT_BACKEND=8081
|
|
PORT_FRONTEND=3001
|
|
APP_DOMAIN=staging.raddatz.cloud
|
|
POSTGRES_PASSWORD=${{ secrets.STAGING_POSTGRES_PASSWORD }}
|
|
MINIO_PASSWORD=${{ secrets.STAGING_MINIO_PASSWORD }}
|
|
MINIO_APP_PASSWORD=${{ secrets.STAGING_MINIO_APP_PASSWORD }}
|
|
OCR_TRAINING_TOKEN=${{ secrets.STAGING_OCR_TRAINING_TOKEN }}
|
|
APP_ADMIN_USERNAME=${{ secrets.STAGING_APP_ADMIN_USERNAME }}
|
|
APP_ADMIN_PASSWORD=${{ secrets.STAGING_APP_ADMIN_PASSWORD }}
|
|
MAIL_HOST=mailpit
|
|
MAIL_PORT=1025
|
|
MAIL_USERNAME=
|
|
MAIL_PASSWORD=
|
|
MAIL_SMTP_AUTH=false
|
|
MAIL_STARTTLS_ENABLE=false
|
|
APP_MAIL_FROM=noreply@staging.raddatz.cloud
|
|
EOF
|
|
|
|
- name: Build images
|
|
run: |
|
|
docker compose \
|
|
-f docker-compose.prod.yml \
|
|
-p archiv-staging \
|
|
--env-file .env.staging \
|
|
--profile staging \
|
|
build
|
|
|
|
- name: Deploy staging
|
|
run: |
|
|
docker compose \
|
|
-f docker-compose.prod.yml \
|
|
-p archiv-staging \
|
|
--env-file .env.staging \
|
|
--profile staging \
|
|
up -d --wait --remove-orphans
|
|
|
|
- name: Smoke test deployed environment
|
|
# Healthchecks confirm containers are healthy; they do NOT confirm the
|
|
# public surface works. This step catches: Caddy not reloaded, DNS
|
|
# missing, HSTS header dropped, /actuator block bypassed.
|
|
run: |
|
|
set -e
|
|
URL="https://staging.raddatz.cloud"
|
|
echo "Smoke test: $URL"
|
|
curl -fsS --max-time 10 "$URL/login" -o /dev/null
|
|
curl -fsS --max-time 10 -I "$URL/" | grep -qi 'strict-transport-security'
|
|
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL/actuator/health")
|
|
[ "$status" = "404" ] || { echo "expected 404 from /actuator/health, got $status"; exit 1; }
|
|
echo "All smoke checks passed"
|
|
|
|
- name: Cleanup env file
|
|
if: always()
|
|
run: rm -f .env.staging
|