ci: Gitea Actions deploy for test (main) and prod (tags) stacks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
70f1890641
commit
aa37b44e00
6 files changed
+195
-4
No files matched your search
@@ -0,0 +1,85 @@
|
||||
# Deployment per Gitea Actions auf den Docker-Host, auf dem auch der Runner läuft.
|
||||
# push auf main -> Images bauen, in die Gitea-Registry pushen, Test-Stack aktualisieren
|
||||
# Tag v* -> dieselben (bereits getesteten) Images als Release taggen, Prod-Stack aktualisieren
|
||||
# Konfiguration/Secrets liegen auf dem Host in /opt/familyplanner/{test,prod}.env (siehe deploy/env.*.example).
|
||||
# Runner läuft als Container: Job-Container brauchen Docker-Socket und /opt/familyplanner (rw) – siehe docs/deploy-runner.md.
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CONFIG_DIR: /opt/familyplanner
|
||||
SERVICES: migrations api web
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.ref_type == 'branch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Registry-Login
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ vars.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
|
||||
- name: Images bauen und pushen
|
||||
working-directory: deploy
|
||||
env:
|
||||
TAG: ${{ github.sha }}
|
||||
run: |
|
||||
docker compose --env-file "$CONFIG_DIR/test.env" build $SERVICES
|
||||
docker compose --env-file "$CONFIG_DIR/test.env" push $SERVICES
|
||||
|
||||
- name: Test-Stack aktualisieren
|
||||
working-directory: deploy
|
||||
env:
|
||||
TAG: ${{ github.sha }}
|
||||
run: |
|
||||
docker network inspect edge >/dev/null 2>&1 || docker network create edge
|
||||
docker compose -p familyplanner-test --env-file "$CONFIG_DIR/test.env" pull $SERVICES
|
||||
docker compose -p familyplanner-test --env-file "$CONFIG_DIR/test.env" up -d --remove-orphans --wait
|
||||
|
||||
prod:
|
||||
if: github.ref_type == 'tag'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Registry-Login
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ vars.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
|
||||
- name: Getestete Images zum Release befördern (kein Neubau)
|
||||
env:
|
||||
REGISTRY: ${{ vars.REGISTRY_HOST }}/${{ vars.REGISTRY_OWNER }}/
|
||||
run: |
|
||||
for svc in migrations api web; do
|
||||
docker pull "${REGISTRY}familyplanner-$svc:${{ github.sha }}"
|
||||
docker tag "${REGISTRY}familyplanner-$svc:${{ github.sha }}" "${REGISTRY}familyplanner-$svc:${{ github.ref_name }}"
|
||||
docker push "${REGISTRY}familyplanner-$svc:${{ github.ref_name }}"
|
||||
done
|
||||
|
||||
- name: Backup vor dem Update
|
||||
working-directory: deploy
|
||||
run: |
|
||||
cp "$CONFIG_DIR/prod.env" .env
|
||||
docker compose -p familyplanner-prod ps sql --status running -q | grep -q . && COMPOSE_PROJECT_NAME=familyplanner-prod sh scripts/backup.sh || echo "Erster Start, kein Backup"
|
||||
rm -f .env
|
||||
|
||||
- name: Caddyfile auf den Host legen (Bind-Mounts löst der Host-Docker auf)
|
||||
run: cp deploy/Caddyfile "$CONFIG_DIR/Caddyfile"
|
||||
|
||||
- name: Prod-Stack aktualisieren
|
||||
working-directory: deploy
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
CADDYFILE: /opt/familyplanner/Caddyfile
|
||||
run: |
|
||||
docker network inspect edge >/dev/null 2>&1 || docker network create edge
|
||||
docker compose -p familyplanner-prod --env-file "$CONFIG_DIR/prod.env" --profile proxy pull $SERVICES
|
||||
docker compose -p familyplanner-prod --env-file "$CONFIG_DIR/prod.env" --profile proxy up -d --remove-orphans --wait
|
||||
+13
-3
@@ -1,12 +1,22 @@
|
||||
# Reverse Proxy mit automatischem HTTPS (Let's Encrypt). Domain kommt aus der Umgebungsvariable DOMAIN.
|
||||
{$DOMAIN} {
|
||||
# Reverse Proxy mit automatischem HTTPS (Let's Encrypt) für Prod und Test auf demselben Host.
|
||||
# Caddy läuft nur im Prod-Stack (Profil "proxy") und erreicht beide Stacks über das externe Docker-Netz "edge"
|
||||
# (Alias <STACK_NAME>-web). Ist der Test-Stack aus, liefert die Test-Domain einfach 502.
|
||||
(common) {
|
||||
encode zstd gzip
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
-Server
|
||||
}
|
||||
}
|
||||
|
||||
{$DOMAIN} {
|
||||
import common
|
||||
# WebSockets (SignalR) werden automatisch durchgereicht.
|
||||
reverse_proxy web:8080
|
||||
reverse_proxy prod-web:8080
|
||||
}
|
||||
|
||||
{$TEST_DOMAIN} {
|
||||
import common
|
||||
reverse_proxy test-web:8080
|
||||
}
|
||||
@@ -101,6 +101,12 @@ services:
|
||||
services__api__http__0: http://api:8080
|
||||
ports:
|
||||
- "${WEB_BIND:-127.0.0.1}:${WEB_PORT:-8080}:8080"
|
||||
# Gemeinsames Netz mit dem Reverse Proxy (Test + Prod auf einem Host): einmalig `docker network create edge`
|
||||
networks:
|
||||
default:
|
||||
edge:
|
||||
aliases:
|
||||
- ${STACK_NAME:-prod}-web
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
@@ -117,19 +123,27 @@ services:
|
||||
image: caddy:2
|
||||
environment:
|
||||
DOMAIN: ${DOMAIN:-localhost}
|
||||
TEST_DOMAIN: ${TEST_DOMAIN:-test.localhost}
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ${CADDYFILE:-./Caddyfile}:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
networks:
|
||||
default:
|
||||
edge:
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
edge:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
sql-data:
|
||||
sql-backup:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Auf dem Docker-Host nach /opt/familyplanner/prod.env kopieren und anpassen (nicht einchecken!).
|
||||
STACK_NAME=prod
|
||||
REGISTRY=gitea.example.com/owner/
|
||||
# TAG setzt der Workflow (Release-Tag, z. B. v1.2.0)
|
||||
|
||||
WEB_PORT=8080
|
||||
WEB_BIND=127.0.0.1
|
||||
|
||||
# Caddy (Profil "proxy" – nur im Prod-Stack, bedient beide Domains)
|
||||
DOMAIN=planner.example.com
|
||||
TEST_DOMAIN=test.planner.example.com
|
||||
|
||||
SQL_SA_PASSWORD=
|
||||
MSSQL_PID=Express
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_EMAIL=
|
||||
FAMILY_NAME=
|
||||
|
||||
VAPID_PUBLIC_KEY=
|
||||
VAPID_PRIVATE_KEY=
|
||||
VAPID_SUBJECT=mailto:admin@example.com
|
||||
GIPHY_API_KEY=
|
||||
CALDAV_REQUIRE_HTTPS=true
|
||||
@@ -0,0 +1,18 @@
|
||||
# Auf dem Docker-Host nach /opt/familyplanner/test.env kopieren und anpassen (nicht einchecken!).
|
||||
STACK_NAME=test
|
||||
# Gitea-Registry inkl. Owner und abschließendem Slash
|
||||
REGISTRY=gitea.example.com/owner/
|
||||
# TAG setzt der Workflow (Commit-SHA)
|
||||
|
||||
WEB_PORT=8081
|
||||
WEB_BIND=127.0.0.1
|
||||
|
||||
SQL_SA_PASSWORD=Test-Anders-als-Prod-2026!
|
||||
MSSQL_PID=Express
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=Test-Erstes-Passwort-2026
|
||||
ADMIN_EMAIL=
|
||||
FAMILY_NAME=Testfamilie
|
||||
|
||||
# Keine echten Integrationen im Test: VAPID/Giphy leer lassen
|
||||
CALDAV_REQUIRE_HTTPS=true
|
||||
@@ -0,0 +1,40 @@
|
||||
# Runner-Konfiguration für den Deploy (Gitea act_runner als Container)
|
||||
|
||||
Der Runner startet jeden Job in einem eigenen Container und spricht dafür mit dem Docker-Daemon des Hosts.
|
||||
Damit der Deploy-Workflow `docker compose` nutzen kann, braucht der Job-Container den Docker-Socket und das Konfigurationsverzeichnis.
|
||||
|
||||
## 1. Runner-Container (Beispiel)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: https://gitea.example.com
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: <token>
|
||||
CONFIG_FILE: /config.yaml
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
## 2. `config.yaml` des Runners
|
||||
|
||||
```yaml
|
||||
runner:
|
||||
labels:
|
||||
# Image mit Docker-CLI + Compose-Plugin
|
||||
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
|
||||
container:
|
||||
options: "-v /var/run/docker.sock:/var/run/docker.sock -v /opt/familyplanner:/opt/familyplanner"
|
||||
valid_volumes:
|
||||
- /var/run/docker.sock
|
||||
- /opt/familyplanner
|
||||
```
|
||||
|
||||
Danach Runner neu starten. `/opt/familyplanner` liegt auf dem **Host** (dort liegen `test.env`, `prod.env`).
|
||||
Der Pfad muss im Job-Container identisch sein, weil der Host-Daemon Mount-Pfade auflöst.
|
||||
|
||||
Hinweis: Jeder Job mit Socket-Zugriff hat Root-Rechte auf dem Host – Runner nur für dieses (private) Repository registrieren.
|
||||
Reference in new issue
Block a user