2026-02-09 17:56:06 +01:00
# Deployment
2026-02-10 12:37:43 +01:00
This document describes the deployment architecture and workflows for the nuzlocke-tracker.
2026-02-09 17:56:06 +01:00
## Architecture Overview
| Component | Dev (Laptop/PC) | Production (Unraid) |
|---|---|---|
| API | `docker-compose.yml` (hot reload) | `docker-compose.prod.yml` (built image) |
2026-02-10 12:37:43 +01:00
| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (nginx, static build) |
| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (bind mount) |
2026-02-09 17:56:06 +01:00
| Container Registry | — | Gitea (user-level packages) |
| Reverse Proxy | — | Nginx Proxy Manager |
2026-02-10 12:37:43 +01:00
| CI/CD | — | Gitea Actions |
2026-02-09 17:56:06 +01:00
### Services
2026-02-10 12:37:43 +01:00
- **Gitea** — self-hosted Git server, container registry, and CI/CD runner. Accessible at `gitea.nerdboden.de` via SSL.
2026-02-09 17:56:06 +01:00
- **Nginx Proxy Manager** — reverse proxy with SSL termination for all services on the Unraid server.
2026-02-10 12:37:43 +01:00
## Branching Strategy
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
| Branch | Purpose | Merge strategy |
|---|---|---|
| `main` | Always production-ready. Deploy workflow builds from here. | Merge commit from `develop` |
| `develop` | Integration branch for day-to-day work. CI runs on push. | Squash merge from `feature/*` |
| `feature/*` | Short-lived branches off `develop` for individual features/fixes. | — |
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Workflow
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
1. Create `feature/xyz` from `develop`
2. Work on the feature, commit, squash merge into `develop`
3. CI runs automatically on `develop` (lint checks)
4. When ready to deploy: merge `develop` into `main` (merge commit)
5. Trigger the deploy workflow via `workflow_dispatch` in Gitea Actions
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## CI/CD (Gitea Actions)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
The project uses two Gitea Actions workflows (GitHub Actions-compatible syntax):
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### CI (`.github/workflows/ci.yml`)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
**Triggers:** Push to `develop` , PRs targeting `develop` (skips bean-only changes)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
**Jobs:**
- `backend-lint` — `ruff check` + `ruff format --check`
- `frontend-lint` — `eslint` + `tsc -b`
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Deploy (`.github/workflows/deploy.yml`)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
**Triggers:** Manual via `workflow_dispatch` (must be on `main` )
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
**Steps:**
1. Login to Gitea container registry
2. Build Docker images (linux/amd64) for API and frontend
3. Push images to the Gitea registry
4. SCP compose file and backup script to Unraid
5. SSH into Unraid and run `docker compose pull && docker compose up -d`
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Secrets (configured in Gitea repo settings)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
| Secret | Purpose |
2026-02-09 17:56:06 +01:00
|---|---|
2026-02-10 12:37:43 +01:00
| `REGISTRY_USERNAME` | Gitea username for pushing images |
| `REGISTRY_PASSWORD` | Gitea access token (`read:package` + `write:package` scopes) |
| `DEPLOY_SSH_KEY` | SSH private key for `root@192.168.1.10` |
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Runner
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
An `act_runner` container runs on Unraid with the Docker socket mounted, registered with the Gitea instance.
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Manual Deployment
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
The `./deploy.sh` script can also be used to deploy from the dev machine:
2026-02-09 17:56:06 +01:00
```bash
2026-02-10 12:37:43 +01:00
# Ensure you're on main with latest changes
2026-02-09 17:56:06 +01:00
git checkout main
2026-02-10 12:37:43 +01:00
./deploy.sh
```
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
The script:
1. Checks you're on `main` with no uncommitted changes
2. Builds Docker images (podman or docker, linux/amd64)
3. Pushes to the Gitea container registry
4. SCPs `docker-compose.prod.yml` and `backup.sh` to Unraid
5. Generates `.env` with a random `POSTGRES_PASSWORD` if missing
6. Pulls images and restarts containers on Unraid via SSH
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Container Registry
Images are hosted on Gitea's built-in container registry as user-level packages:
```
gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
2026-02-09 17:56:06 +01:00
```
2026-02-10 12:37:43 +01:00
### Authentication
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
1. Create a Gitea access token at **Settings > Applications ** with `read:package` and `write:package` scopes.
2. Log in: `docker login gitea.nerdboden.de` (username + token as password).
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Production Environment
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### File layout on Unraid
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
```
/mnt/user/appdata/nuzlocke-tracker/
├── docker-compose.yml # production compose (synced from repo)
├── .env # POSTGRES_PASSWORD (auto-generated)
├── backup.sh # database backup script (synced from repo)
├── backups/ # pg_dump backups (daily, 7-day retention)
│ └── cron.log
└── data/
└── postgres/ # PostgreSQL data (bind mount)
```
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Environment variables
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
The `.env` file is auto-generated on first deploy with a random `POSTGRES_PASSWORD` . The compose file references it for both the database and API connection string.
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Database
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
PostgreSQL 16 with data persisted via bind mount at `./data/postgres/` .
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
- Migrations run automatically on API container startup (`alembic upgrade head` )
- Daily backups via `pg_dump` scheduled through the Unraid User Scripts plugin (03:00, 7-day retention)
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Backup
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
Backups are created by `backup.sh` and stored in `/mnt/user/appdata/nuzlocke-tracker/backups/` . The script is scheduled via the Unraid User Scripts plugin.
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
### Restore
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
```bash
cd /mnt/user/appdata/nuzlocke-tracker
gunzip -c backups/nuzlocke-YYYYMMDD-HHMMSS.sql.gz | \
docker compose exec -T db psql -U postgres nuzlocke
```
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Rollback
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
Currently images are tagged as `latest` only. To roll back:
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
1. Revert the merge commit on `main`
2. Trigger the deploy workflow (or run `./deploy.sh` ) to rebuild and push
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
For more granular rollback, consider adding commit-hash tags to images in the future.
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
## Nginx Proxy Manager
2026-02-09 17:56:06 +01:00
2026-02-10 12:37:43 +01:00
NPM runs on Unraid and handles SSL termination and routing:
- `nuzlocke.nerdboden.de` → nuzlocke-tracker frontend (port 9080)
- `gitea.nerdboden.de` → Gitea