The artifact step failed the mutation job: upload-artifact@v4 bundles
@actions/artifact v2, which hard-aborts on any non-github.com server ("not
supported on GHES"), even though Gitea 1.25 stores artifacts fine. @v3 uses the
older protocol Gitea speaks and has no GHES guard — a drop-in swap (same inputs).
Document it as gotcha §4 and correct the CI runbook note.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
127 lines
6.0 KiB
Markdown
127 lines
6.0 KiB
Markdown
# Gitea Actions gotchas
|
|
|
|
How our CI (Gitea Actions on the hosted **`ubuntu-latest`** runner) differs from a
|
|
local run, and the workarounds in this repo. Referenced by `CLAUDE.md` §8.7/§15.
|
|
|
|
**One root cause sits under most of this:** the runner executes the job **inside a
|
|
container**, so when a step runs `docker compose up`, Compose starts the stack as
|
|
**sibling containers** on the host's daemon. Anything that assumes the job and
|
|
those containers share a filesystem — or a `localhost` — breaks.
|
|
|
|
| Gotcha | Fix | Lives in |
|
|
|---|---|---|
|
|
| Bind-mounted config arrives empty | `docker cp` config into external volumes | `infra/seed-config.sh` |
|
|
| `docker compose up --wait` is unsupported / flaky | poll health with `docker inspect` | `infra/wait-healthy.sh` |
|
|
| `pg_isready` passes before PostGIS is ready | add a `PostGIS_Version()` probe | the db healthchecks |
|
|
| `upload-artifact@v4` fails ("not supported on GHES") | pin `@v3` | `.gitea/workflows/ci.yaml` (`mutation` job) |
|
|
|
|
---
|
|
|
|
## 1. Bind mounts don't reach the containers
|
|
|
|
**Symptom** — green locally, but `compose-smoke` fails with:
|
|
|
|
```
|
|
oz-init-1 | CommandError: Yaml file `/app/setup_configuration/data.yaml` does not exist.
|
|
```
|
|
|
|
Migrations run fine; only the step that reads a *mounted* file fails. The same
|
|
trap hits `nrc-init`, `flowable-init`, and `keycloak`.
|
|
|
|
**Why** — a relative bind mount like `./openzaak/setup_configuration:/app/...` is
|
|
resolved by Compose to a path *inside the job container*
|
|
(`/workspace/.../setup_configuration`). The daemon then looks for that path on
|
|
*its own host*, doesn't find it, and mounts an **empty directory**. (It works on a
|
|
runner that executes jobs on the host — which is why moving to `ubuntu-latest`
|
|
exposed it.)
|
|
|
|
**Fix** — use the upstream images verbatim (no build) and stream config into
|
|
**external named volumes** with `docker cp`, which copies over the Docker API and
|
|
so works wherever the daemon runs. `infra/seed-config.sh` creates each volume,
|
|
mounts it in a throwaway helper, and copies the files in:
|
|
|
|
| Asset | Volume | Mounted at |
|
|
|---|---|---|
|
|
| OpenZaak `data.yaml` | `rr-oz-config` | `oz-init:/app/setup_configuration` |
|
|
| Keycloak realms | `rr-kc-realms` | `keycloak:/opt/keycloak/data/import` |
|
|
| `registratie.bpmn` | `rr-fl-bpmn` | `flowable-init:/work` |
|
|
|
|
The volumes are `external: true` with fixed names, so they resolve identically
|
|
under docker compose and podman-compose. `make` seeds before every `up`; `make
|
|
down` removes them. (Open Notificaties needs nothing — `nrc-init` migrates only.)
|
|
|
|
**Consequence — bare `docker compose up` can't self-seed external volumes:**
|
|
|
|
- **CI / Linux / macOS:** `make up` or `make smoke` (seed, then start).
|
|
- **No-make / Windows:** `infra/docker-compose.local.yml` — a twin stack that
|
|
**bind-mounts** the config instead. Bind mounts are fine *locally* because a
|
|
local daemon can see your working directory, so
|
|
`docker compose -f infra/docker-compose.local.yml up -d` just works.
|
|
|
|
**Why not the obvious alternatives**
|
|
|
|
- *Bake config into an image* (incl. an inline Dockerfile) — `docker compose up`
|
|
would then work unaided, but it's a build; we wanted the upstream images as-is.
|
|
- *Compose `configs:` with inline `content`* — Compose writes a client-side temp
|
|
file and bind-mounts it, hitting the exact same problem.
|
|
- *A host-executing runner* — bind mounts would work with zero seeding, but it
|
|
reintroduces a self-hosted runner and undoes the move to `ubuntu-latest`.
|
|
|
|
---
|
|
|
|
## 2. Readiness: poll health, don't use `--wait`
|
|
|
|
`docker compose up --wait` looks ideal but fails us three ways:
|
|
|
|
- **podman-compose doesn't implement it** (`unrecognized arguments: --wait`) — so
|
|
it would break local dev.
|
|
- A project-wide `--wait` **treats a one-shot exiting `0` as a failure** unless
|
|
something `depends_on` it with `service_completed_successfully`. `flowable-init`
|
|
deploys the BPMN and exits with no dependant, so `--wait` fails the moment it
|
|
does — last line `container infra-flowable-init-1 exited (0)`.
|
|
- The containerized runner **can't reach published host ports**, so an external
|
|
`curl localhost:8080/health` can't work either.
|
|
|
|
**Fix** — `infra/wait-healthy.sh` polls each durable service (`openzaak nrc-web
|
|
acl bff`, listed as `WAIT_SVCS` in the `Makefile`) with `docker ps` + `docker
|
|
inspect '{{.State.Health.Status}}'` until it reports `healthy`. It uses only
|
|
primitives both runtimes support, reads the **in-container** healthcheck (no host
|
|
port needed), and ignores the one-shots (they only need to have run).
|
|
`WAIT_TIMEOUT` defaults to 420 s — enough for the cold OpenZaak migrate (~90 s)
|
|
plus app start.
|
|
|
|
---
|
|
|
|
## 3. `pg_isready` passes before PostGIS is ready
|
|
|
|
`pg_isready` succeeds as soon as the TCP port is open — *before* the
|
|
`postgis/postgis` image has finished running `CREATE EXTENSION postgis`. An init
|
|
container that starts migrating in that window can fail on a missing PostGIS. So
|
|
the db healthchecks add a `SELECT PostGIS_Version()` probe, making dependents wait
|
|
for the extension, not just the port.
|
|
|
|
---
|
|
|
|
## 4. `actions/upload-artifact@v4` refuses to run on Gitea
|
|
|
|
**Symptom** — the `mutation` job's `make mutation` step passes (95% score), but the
|
|
upload step right after it fails the job:
|
|
|
|
```
|
|
::error::@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+
|
|
are not currently supported on GHES.
|
|
❌ Failure - Main https://github.com/actions/upload-artifact@v4
|
|
```
|
|
|
|
**Why** — `upload-artifact@v4` bundles `@actions/artifact` v2, which inspects the
|
|
server URL and **hard-aborts on anything that isn't `github.com`**, treating Gitea
|
|
as an unsupported GitHub Enterprise Server. The check fires regardless of whether
|
|
the Gitea server can actually store artifacts (1.24+ can). It is the *action*, not
|
|
the server, that refuses.
|
|
|
|
**Fix** — pin **`actions/upload-artifact@v3`** (and `download-artifact@v3` if ever
|
|
needed). v3 uses the older artifact protocol that Gitea implements, and has no GHES
|
|
guard. Inputs are the same (`name`, `path`, `if-no-files-found`), so it is a drop-in
|
|
swap. Do **not** bump to `@v4` until act_runner advertises github.com-compatible
|
|
artifact support.
|