Files
directus/.planning/phase-1-slice-1-schema/08-gitea-ci-dryrun.md
T
julian 387c3c4cfa Task 1.1 — Project scaffold
Phase 1 task 1.1 lands. Directus 11.17.4 boots locally end-to-end
against a TimescaleDB+PostGIS container; admin UI serves at :8055,
admin bootstrap from env vars works, named volumes preserve data
across down/up cycles.

Scaffold:
- Dockerfile — FROM directus/directus:11.17.4. Pre-installs
  postgresql16-client (ahead of task 1.2's db-init runner needing psql).
  Bakes in /directus/snapshots, /directus/db-init, /directus/scripts,
  /directus/extensions, /directus/entrypoint.sh.
- compose.dev.yaml — db (timescale/timescaledb-ha:pg16.6-ts2.17.2-all)
  + directus (local build), healthchecks, named volumes
  directus-pg-data + directus-uploads.
- entrypoint.sh — placeholder using upstream's actual flow
  (node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs);
  the real db-init -> schema apply -> start wrapper lands in task 1.7.
- package.json — scripts-only (dev, dev:down, dev:reset,
  schema:snapshot, schema:apply, db:init), no runtime deps.
- .env.example — sectioned, fully documented, KEY/SECRET marked
  required with generation hints.
- .gitignore, .dockerignore — match the processor service conventions.
- snapshots/, db-init/, scripts/, extensions/ — empty with .gitkeep,
  filled by later Phase 1 tasks (1.3, 1.6) and Phase 5.

Lessons locked in (against the empirical pnpm dev boot):
- timescale/timescaledb-ha:pg16-latest does NOT exist on Docker Hub.
  Pin a concrete version (we used pg16.6-ts2.17.2-all).
- This image's data directory is /home/postgres/pgdata/data, not
  /pgdata or /var/lib/postgresql/data. PGDATA env var and the volume
  mount must both target it.
- The -all variant bundles PostGIS binaries but the extension is not
  auto-created on the directus database; CREATE EXTENSION lands in
  Phase 2 alongside the geofences/SLZs/waypoints collections.
- The upstream image's CMD is bootstrap + pm2-runtime, not a simple
  cli.js start. Bypassing pm2 would lose crash recovery.

These corrections folded into 01-project-scaffold.md (deliverable line
+ Done section), 08-gitea-ci-dryrun.md (CI service tag), and the
inline comments in compose.dev.yaml so future implementers don't
re-discover them.

Status: ROADMAP marks 1.1 done, Phase 1 in progress, 1.2 next.
2026-05-01 21:29:13 +02:00

6.3 KiB

Task 1.8 — Gitea CI dry-run workflow

Phase: 1 — Slice 1 schema + deploy pipeline Status: Not started Depends on: 1.7 Wiki refs: docs/wiki/entities/directus.md (Schema management section)

Goal

Build a Gitea Actions workflow that on push to main (when relevant paths change): builds the image, spins up a throwaway Postgres + TimescaleDB in CI, runs the entrypoint flow as a dry-run to catch snapshot/migration breakage, and only publishes the image to the registry if the dry-run succeeds. Mirrors the processor and tcp-ingestion workflow shape.

Deliverables

  • .gitea/workflows/build.yml:
    name: Build directus image
    
    on:
      push:
        branches: [main]
        paths:
          - 'snapshots/**'
          - 'db-init/**'
          - 'extensions/**'
          - 'scripts/**'
          - 'entrypoint.sh'
          - 'Dockerfile'
          - '.gitea/workflows/build.yml'
      workflow_dispatch:
    
    jobs:
      build-and-publish:
        runs-on: ubuntu-22.04
        services:
          postgres:
            image: timescale/timescaledb-ha:pg16.6-ts2.17.2-all  # match compose.dev.yaml; :pg16-latest does NOT exist on Docker Hub
            env:
              POSTGRES_USER: directus
              POSTGRES_PASSWORD: directus
              POSTGRES_DB: directus
            ports: ['5432:5432']
            options: >-
              --health-cmd "pg_isready -U directus"
              --health-interval 5s
              --health-timeout 5s
              --health-retries 10
    
        steps:
          - uses: actions/checkout@v4
    
          - name: Build image
            run: docker build -t trm-directus:ci .
    
          - name: Dry-run boot against throwaway Postgres
            env:
              DB_HOST: postgres
              DB_PORT: 5432
              DB_USER: directus
              DB_PASSWORD: directus
              DB_DATABASE: directus
              KEY: ci-key-not-secret
              SECRET: ci-secret-not-secret
              ADMIN_EMAIL: ci@example.com
              ADMIN_PASSWORD: ci-password-not-secret
              PUBLIC_URL: http://localhost:8055
            run: |
              docker run --rm \
                -e DB_CLIENT=pg \
                -e DB_HOST=$DB_HOST -e DB_PORT=$DB_PORT \
                -e DB_USER=$DB_USER -e DB_PASSWORD=$DB_PASSWORD -e DB_DATABASE=$DB_DATABASE \
                -e KEY=$KEY -e SECRET=$SECRET \
                -e ADMIN_EMAIL=$ADMIN_EMAIL -e ADMIN_PASSWORD=$ADMIN_PASSWORD \
                -e PUBLIC_URL=$PUBLIC_URL \
                --network host \
                --entrypoint bash \
                trm-directus:ci \
                -c '/directus/scripts/apply-db-init.sh && /directus/scripts/schema-apply.sh && echo "dry-run ok"'
    
          - name: Login to Gitea registry
            uses: docker/login-action@v3
            with:
              registry: git.dev.microservices.al
              username: ${{ secrets.REGISTRY_USERNAME }}
              password: ${{ secrets.REGISTRY_PASSWORD }}
    
          - name: Tag and push
            run: |
              docker tag trm-directus:ci git.dev.microservices.al/trm/directus:main
              docker tag trm-directus:ci git.dev.microservices.al/trm/directus:${{ github.sha }}
              docker push git.dev.microservices.al/trm/directus:main
              docker push git.dev.microservices.al/trm/directus:${{ github.sha }}
    
          - name: Trigger Portainer redeploy (optional)
            if: secrets.PORTAINER_WEBHOOK_URL != ''
            run: curl -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"
    

Specification

  • Dry-run runs the entrypoint scripts only, not directus start. Starting the server and waiting for it to serve is slow and unnecessary — the goal is to catch DDL / snapshot apply errors. Override the ENTRYPOINT and run the two scripts directly.
  • Service container is the throwaway Postgres. services: block in Gitea Actions (compatible syntax with GitHub Actions). Use the pinned TimescaleDB image; mismatch with prod hides bugs.
  • Path filter on on.push.paths keeps CI quiet for unrelated repo changes (docs-only commits, etc.). Mirrors the processor workflow.
  • Two image tags published: :main (always points at latest main) and :<sha> (specific commit, immutable). The deploy stack can pin to either.
  • Portainer webhook is optional (gated by secret presence). If unset, no auto-deploy.
  • No integration tests in CI for Phase 1. The dry-run boot is the integration test — it proves the snapshot+db-init combination works against a fresh Postgres. Phase 5+ adds extension-specific tests as those land.
  • Required Gitea secrets:
    • REGISTRY_USERNAME, REGISTRY_PASSWORD — for the image push.
    • PORTAINER_WEBHOOK_URL — optional, for auto-deploy.

Acceptance criteria

  • Workflow file is committed at .gitea/workflows/build.yml.
  • First push to main after this lands triggers the workflow.
  • Workflow steps in order: checkout → build → dry-run boot → registry login → tag/push → optional Portainer ping.
  • Dry-run step exits 0 with logs showing "db-init complete" and "schema apply: no changes" (after the snapshot has been applied once, subsequent runs against fresh Postgres still apply from scratch — verify the apply step works in both cases).
  • Intentionally break the snapshot (manually edit snapshots/schema.yaml to a malformed YAML) → workflow fails at the dry-run step → image is NOT pushed.
  • Intentionally break a migration (introduce SQL syntax error in db-init/) → workflow fails at the dry-run step → image is NOT pushed.
  • Push a docs-only change → workflow does NOT trigger.
  • Image pushed to registry under git.dev.microservices.al/trm/directus:main and :<sha>.
  • Portainer webhook fires if configured.

Risks / open questions

  • Gitea Actions services: syntax compatibility. Gitea's runner is mostly GitHub-Actions-compatible but has historically had quirks with the services: block (especially around image pulls from private registries). If the throwaway Postgres can't be brought up via services:, fall back to a docker run step that backgrounds the container and a wait-loop on pg_isready. Document the chosen approach.
  • Network access between job container and service container. --network host is the simplest solution if Gitea's runner allows it. If not, use the Docker network created by the runner and reference the service by name (postgres:5432).

Done

(Fill in commit SHA + one-line note when this lands.)