All checks were successful
Deploy to NAS / deploy (push) Successful in 44s
- README.md: rewrite with accurate setup steps including all lessons learned - CLAUDE.md: update with working CI/CD patterns and known pitfalls - gitea-actions.md: new file documenting Gitea Actions usage, expressions, act_runner config, and troubleshooting for future projects Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Infrastruktur & Deployment
Architektur
Synology NAS (192.168.1.4)
├── Gitea :3000 — Git + CI/CD
├── act_runner — Gitea Actions Runner
├── /volume2/docker/shared/
│ ├── postgres:16-alpine :5433 — Geteilte DB (alle Projekte)
│ └── pgdata/ — Persistente Daten
└── /volume2/docker/<projekt>/
├── <app>-Container :<port> — Anwendung
├── uploads/ — Persistente Uploads
└── .env — Projekt-Secrets
Datenbankverbindung aus Containern: host-gateway:5433
1. Shared PostgreSQL einrichten (einmalig)
sudo mkdir -p /volume2/docker/shared/pgdata
sudo cp infra/docker-compose.yml /volume2/docker/shared/
echo "POSTGRES_PASSWORD=<sicheres-passwort>" | sudo tee /volume2/docker/shared/.env
cd /volume2/docker/shared && sudo docker compose up -d
Datenbank anlegen:
sudo docker exec -it shared-postgres-1 psql -U postgres
CREATE DATABASE <dbname>;
CREATE USER <dbuser> WITH PASSWORD '<passwort>';
GRANT ALL PRIVILEGES ON DATABASE <dbname> TO <dbuser>;
GRANT ALL ON SCHEMA public TO <dbuser>; -- wichtig für PostgreSQL 15+
\q
2. act_runner einrichten (einmalig)
Token holen: Gitea → Site-Administration → Actions → Runner → Runner erstellen
sudo docker run -d \
--name gitea-runner \
--restart unless-stopped \
--network host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /volume2/docker/gitea-runner:/data \
-e GITEA_INSTANCE_URL=http://192.168.1.4:3000 \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
-e GITEA_RUNNER_NAME=nas-runner \
-e GITEA_RUNNER_LABELS=self-hosted,linux,amd64 \
gitea/act_runner:latest
Wichtig:
GITEA_INSTANCE_URLmuss die NAS-IP sein, nichtlocalhost— Job-Container könnenlocalhostnicht auflösen.
/volume2/docker/gitea-runner/config.yaml anlegen:
runner:
name: "nas-runner"
container:
valid_volumes:
- /volume2/docker
Runner neu starten: sudo docker restart gitea-runner
3. Neues Projekt deployen
3.1 Verzeichnis anlegen
sudo mkdir -p /volume2/docker/<projekt>/uploads
3.2 docker-compose.yml (im Repo)
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "${APP_PORT:-8080}:8080"
extra_hosts:
- "host-gateway:host-gateway"
environment:
DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@host-gateway:5433/${DB_NAME}
volumes:
- /volume2/docker/<projekt>/uploads:/uploads
restart: unless-stopped
3.3 Gitea Secrets & Variables
Secrets (Repository → Einstellungen → Actions → Secrets):
| Secret | Wert |
|---|---|
DB_PASSWORD |
Datenbankpasswort |
Variables (Repository → Einstellungen → Actions → Variables):
| Variable | Wert |
|---|---|
DEPLOY_DIR |
/volume2/docker/<projekt> |
DB_USER |
<dbuser> |
DB_NAME |
<dbname> |
APP_PORT |
<port> |
3.4 Gitea Actions Workflow (.gitea/workflows/deploy.yml)
name: Deploy to NAS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: self-hosted
container:
image: docker:latest
options: -v /volume2/docker:/volume2/docker
steps:
- name: Pull code
run: |
if [ -d "${{ vars.DEPLOY_DIR }}/.git" ]; then
git -C ${{ vars.DEPLOY_DIR }} pull
else
git clone http://192.168.1.4:3000/<org>/<repo>.git ${{ vars.DEPLOY_DIR }}
fi
- name: Write .env
run: printf 'DB_PASSWORD=%s\n' '${{ secrets.DB_PASSWORD }}' > ${{ vars.DEPLOY_DIR }}/.env
- name: Build & Deploy
run: docker compose -f ${{ vars.DEPLOY_DIR }}/docker-compose.yml up --build -d
- name: Health check
run: |
sleep 15
wget -qO- http://192.168.1.4:${{ vars.APP_PORT }}/healthz || exit 1
4. Synology-spezifische Hinweise
| Problem | Lösung |
|---|---|
permission denied bei Docker-Befehlen |
Immer sudo docker ... verwenden |
| Ports < 1024 nicht bindbar | Internen Port > 1024 wählen, z.B. LISTEN_PORT: 8080 |
| Docker-Socket im Container Manager UI nicht mountbar | docker run via SSH ausführen |
act_runner kann localhost:3000 nicht erreichen |
NAS-IP statt localhost in GITEA_INSTANCE_URL |
| Volume-Mount wird ignoriert | valid_volumes in config.yaml eintragen |
| Session-Cookies funktionieren nicht | Secure: false setzen bei HTTP-Deployment |
GRANT ALL PRIVILEGES reicht nicht |
Zusätzlich GRANT ALL ON SCHEMA public ausführen (PG 15+) |
5. Wartung
# Logs
sudo docker compose -f /volume2/docker/shared/docker-compose.yml logs -f postgres
sudo docker compose -f /volume2/docker/<projekt>/docker-compose.yml logs -f
# Backup
sudo docker exec shared-postgres-1 pg_dump -U postgres <dbname> \
> /volume2/docker/shared/backup_$(date +%Y%m%d)_<dbname>.sql
# Alle Container stoppen
sudo docker compose -f /volume2/docker/shared/docker-compose.yml down
sudo docker compose -f /volume2/docker/<projekt>/docker-compose.yml down
6. Dienste & Ports
| Dienst | Port | Beschreibung |
|---|---|---|
| Gitea | 3000 | Git-Server + CI/CD |
| PostgreSQL | 5433 | Geteilte DB (intern: 5432) |
| Pamietnik | 9050 | Tagebuch-App |