Files
moneyfyandClaude Opus 5 87bee9b72a build: Docker-Images, Compose-Setup und Gitea-Workflows
- Mehrstufige Dockerfiles je Dienst mit Stufen für Entwicklung und Betrieb,
  beide Images laufen als unprivilegierter Benutzer
- Backend-Entrypoint wartet auf die Datenbank, migriert und seedt nur bei
  leerem Kategoriebaum (neues Flag --if-empty)
- docker-compose.yml mit Netz-Trennung, Healthchecks und benannten Volumes;
  die Datenbank ist ausschließlich im internen Netz erreichbar
- nginx liefert das Bundle aus und reicht /api weiter; index.html ungecacht,
  gehashte Assets ein Jahr
- Gitea-Workflows: ci.yml für Lint, Tests und Bundle-Bau, build.yml für die
  Images nach linux/amd64 ohne Deploy-Schritt
- docs/runner.md und docs/deployment.md, .dockerignore je Dienst

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
2026-09-09 17:14:44 +02:00

271 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea-Actions-Runner einrichten
Die Workflows unter `.gitea/workflows/` brauchen einen `act_runner`, der Docker
erreichen kann. Diese Anleitung richtet ihn als Container auf `docker-srv001`
ein. Sie ist vollständig von der Tokenbeschaffung bis zur Fehlersuche.
## 1. Registrierungstoken holen
Es gibt zwei Möglichkeiten:
**Nur für dieses Repository** (empfohlen, wenn der Runner ausschließlich moneyfy
bauen soll):
1. In Gitea das Repository `menzeljonas/moneyfy` öffnen.
2. *Settings → Actions → Runners* aufrufen.
3. **Create new Runner** anklicken. Gitea zeigt das Registrierungstoken an eine
Zeichenkette aus etwa 40 Zeichen.
4. Token kopieren. Es lässt sich nur einmal verwenden.
**Instanzweit** (der Runner steht dann allen Repositories zur Verfügung):
1. Als Administrator anmelden.
2. *Site Administration → Actions → Runners* aufrufen.
3. Ebenfalls **Create new Runner**, Token kopieren.
Falls Actions in der Instanz noch abgeschaltet sind, in der `app.ini` des
Gitea-Servers ergänzen und Gitea neu starten:
```ini
[actions]
ENABLED = true
```
## 2. Runner als Container betreiben
Auf `docker-srv001` ein eigenes Verzeichnis anlegen:
```bash
mkdir -p /opt/gitea-runner/data
cd /opt/gitea-runner
```
`docker-compose.yml`:
```yaml
name: gitea-runner
services:
act-runner:
image: gitea/act_runner:0.2.11
container_name: gitea-act-runner
restart: unless-stopped
environment:
GITEA_INSTANCE_URL: https://git.menzel.center
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
GITEA_RUNNER_NAME: docker-srv001
# Muss zu den Labels in config.yaml passen.
GITEA_RUNNER_LABELS: ubuntu-latest,ubuntu-22.04
CONFIG_FILE: /config.yaml
volumes:
# Der Runner startet die Job-Container über den Docker-Socket des Hosts.
- /var/run/docker.sock:/var/run/docker.sock
- ./config.yaml:/config.yaml:ro
- ./data:/data
```
`.env` daneben:
```bash
GITEA_RUNNER_REGISTRATION_TOKEN=<Token aus Schritt 1>
```
> Der gemountete Docker-Socket gibt dem Runner faktisch Root-Rechte auf dem Host.
> Das ist für einen Runner, der eigene Images baut, üblich betreibe ihn
> deshalb nur mit vertrauenswürdigen Repositories.
## 3. `config.yaml`
Im selben Verzeichnis anlegen:
```yaml
log:
level: info
runner:
file: /data/.runner
capacity: 2
timeout: 3h
# Die linke Seite ist das Label aus `runs-on`, rechts das Image, in dem der Job läuft.
labels:
- "ubuntu-latest:docker://node:20-bookworm"
- "ubuntu-22.04:docker://node:20-bookworm"
cache:
enabled: true
dir: /data/cache
# Adresse, unter der die Job-Container den Cache-Server des Runners erreichen.
# Leer lassen heißt: automatisch ermitteln. Bei mehreren Netzen hier die IP
# des Hosts im Runner-Netz eintragen.
host: ""
port: 0
container:
# Job-Container in dasselbe Netz hängen wie der Runner.
network: bridge
privileged: false
# Nötig, damit `actions/cache` und `docker/build-push-action` Pfade einblenden dürfen.
valid_volumes:
- /data/cache
docker_host: "-"
# Bei selbstsigniertem Zertifikat auf true setzen, siehe Abschnitt 6.
force_pull: false
```
`docker_host: "-"` bedeutet: den Socket des Runners an die Job-Container
weiterreichen. Genau das braucht `build.yml`, um Images zu bauen.
Starten:
```bash
docker compose up -d
docker compose logs -f
```
Beim ersten Start registriert sich der Runner und legt `/data/.runner` an. Ab
dann wird das Registrierungstoken nicht mehr gebraucht.
## 4. Repo-Secret `REGISTRY_TOKEN` anlegen
Der Build-Workflow lädt Images in die Gitea-Registry und braucht dafür ein
Token mit Schreibrecht auf Pakete:
1. In Gitea auf *Benutzereinstellungen → Applications → Generate New Token*.
2. Namen vergeben, etwa `moneyfy-registry`.
3. Unter *Select permissions* bei **package** auf `Read and Write` stellen.
4. **Generate Token**, Wert kopieren er wird nur einmal angezeigt.
5. Im Repository *Settings → Actions → Secrets → Add Secret*:
- Name: `REGISTRY_TOKEN`
- Wert: das kopierte Token
Der Workflow meldet sich mit `${{ github.actor }}` an, also mit dem Benutzer,
der den Lauf ausgelöst hat. Dieser Benutzer muss Schreibrecht auf die Pakete des
Namensraums `menzeljonas` haben.
## 5. Verifikation
1. **Runner erscheint als „idle“:** in Gitea unter *Settings → Actions → Runners*.
Dort stehen Name (`docker-srv001`), Labels und Status.
2. **Testworkflow läuft:** einen Commit auf einen beliebigen Branch schieben.
Unter *Actions* muss `CI` starten und beide Jobs grün abschließen.
3. **Image landet in der Registry:** auf `main` pushen und danach
`https://git.menzel.center/menzeljonas/-/packages` öffnen. Dort müssen
`moneyfy-backend` und `moneyfy-frontend` mit den Tags `main` und
`sha-<kurz>` auftauchen.
4. **Versions-Tag prüfen:**
```bash
git tag -a v0.1.0 -m "Erste Version"
git push origin v0.1.0
```
Danach müssen zusätzlich `0.1.0`, `0.1` und `latest` vorhanden sein.
5. **Image von Hand ziehen** (prüft nebenbei die Registry-Anmeldung):
```bash
docker login git.menzel.center
docker pull git.menzel.center/menzeljonas/moneyfy-backend:latest
```
## 6. Fehlersuche
### `docker: command not found` im Job
Das Job-Image bringt keinen Docker-Client mit. Zwei Wege:
- In `config.yaml` sicherstellen, dass `docker_host: "-"` gesetzt ist der
Runner reicht dann seinen Socket weiter.
- Für Jobs, die Docker brauchen, ein Image mit Client verwenden, etwa das Label
`ubuntu-latest:docker://catthehacker/ubuntu:act-22.04`. Die Actions
`docker/setup-buildx-action` und `docker/build-push-action` bringen den Rest mit.
Prüfen lässt sich das mit einem Wegwerf-Schritt:
```yaml
- run: docker version
```
### Registry-Anmeldung schlägt mit selbstsigniertem Zertifikat fehl
Meldung: `x509: certificate signed by unknown authority`.
Auf dem Host das CA-Zertifikat hinterlegen und den Docker-Daemon neu starten:
```bash
mkdir -p /etc/docker/certs.d/git.menzel.center
cp menzel-center-ca.crt /etc/docker/certs.d/git.menzel.center/ca.crt
systemctl restart docker
```
Zusätzlich muss der Runner-Container die CA kennen, weil buildx im Container
läuft. Im Compose des Runners einblenden:
```yaml
volumes:
- /usr/local/share/ca-certificates/menzel-center-ca.crt:/usr/local/share/ca-certificates/menzel-center-ca.crt:ro
```
und im Runner einmalig `update-ca-certificates` ausführen. Ein gültiges
Let's-Encrypt-Zertifikat auf dem Gitea-Host erspart diesen ganzen Abschnitt.
### `DOCKER_HOST` im DinD-Betrieb
Wer statt des Host-Sockets einen Docker-in-Docker-Dienst betreibt, ergänzt
diesen als zweiten Service und setzt im Runner:
```yaml
environment:
DOCKER_HOST: tcp://docker-in-docker:2376
DOCKER_CERT_PATH: /certs/client
DOCKER_TLS_VERIFY: "1"
volumes:
- ./certs:/certs
```
In `config.yaml` dann `docker_host: tcp://docker-in-docker:2376`. DinD ist
langsamer und der Schichten-Cache geht bei jedem Neustart verloren der
gemountete Host-Socket ist für ein Homelab die pragmatischere Wahl.
### Der Cache des Runners frisst Speicherplatz
`/opt/gitea-runner/data/cache` und die Build-Schichten wachsen mit jedem Lauf.
Belegung ansehen:
```bash
du -sh /opt/gitea-runner/data/cache
docker system df
```
Aufräumen:
```bash
# Nicht mehr referenzierte Schichten und Build-Cache älter als eine Woche
docker buildx prune --filter "until=168h" -f
docker image prune -f
# Cache-Verzeichnis des Runners leeren
docker compose -f /opt/gitea-runner/docker-compose.yml down
rm -rf /opt/gitea-runner/data/cache/*
docker compose -f /opt/gitea-runner/docker-compose.yml up -d
```
Regelmäßig per Cron:
```cron
0 4 * * 0 docker buildx prune --filter "until=168h" -f >/dev/null 2>&1
```
### Job bleibt in „waiting“ hängen
Die Labels stimmen nicht überein. `runs-on: ubuntu-latest` im Workflow braucht
ein Label `ubuntu-latest` am Runner. Nach einer Änderung in `config.yaml` muss
der Runner neu registriert werden:
```bash
docker compose down
rm data/.runner
# neues Registrierungstoken in .env eintragen
docker compose up -d
```