From 132dac0a2e882d5d5d8139cf4a1da4472d87d50c Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Tue, 10 Feb 2026 12:37:43 +0100 Subject: [PATCH] Update deployment docs, complete Deployment Strategy epic Rewrite DEPLOYMENT.md to reflect current state (CI/CD, backups, merge strategy). Expand CI paths-ignore to skip docs, license, gitignore, and deploy workflow changes. Add merge strategy to CLAUDE.md. Co-Authored-By: Claude Opus 4.6 --- ...locke-tracker-ahza--deployment-strategy.md | 10 +- ...--implement-gitea-actions-cicd-pipeline.md | 10 +- ...-re0m--document-the-deployment-workflow.md | 4 +- .github/workflows/ci.yml | 8 + CLAUDE.md | 2 + DEPLOYMENT.md | 228 +++++++++--------- 6 files changed, 138 insertions(+), 124 deletions(-) diff --git a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md index 36b5766..0b039d6 100644 --- a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md +++ b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-ahza title: Deployment Strategy -status: in-progress +status: completed type: epic priority: normal created_at: 2026-02-09T14:03:53Z -updated_at: 2026-02-10T08:16:36Z +updated_at: 2026-02-10T11:36:07Z --- Define and implement a deployment strategy for running the nuzlocke-tracker in production on a local Unraid server while keeping laptop/PC as the development environment. @@ -47,12 +47,12 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p - [x] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow - [x] **Update CLAUDE.md with branching rules** — once the branching structure is in place, add instructions to CLAUDE.md that the branching strategy must be adhered to (always work on feature branches, never commit directly to `main`, merge flow is `feature/*` → `develop` → `main`) -- [ ] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package +- [x] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package - [x] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the Gitea container registry, production env vars, no source volume mounts, proper restart policies - [x] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode - [x] **Create deploy script** — `./deploy.sh` builds images (podman/docker, linux/amd64), pushes to Gitea registry, SCPs compose file, generates `.env` if needed, pulls and starts containers via SSH - [x] **Configure Nginx Proxy Manager** — add proxy host entries for Gitea and the nuzlocke-tracker frontend/API on the appropriate ports - [x] **Environment & secrets management** — deploy script auto-generates `.env` with `POSTGRES_PASSWORD` on Unraid if missing; file lives at `/mnt/user/appdata/nuzlocke-tracker/.env` -- [ ] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability +- [x] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability - [x] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL data (e.g., cron + `pg_dump` script on Unraid) -- [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance \ No newline at end of file +- [x] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md b/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md index af3b02a..89e2b73 100644 --- a/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md +++ b/.beans/nuzlocke-tracker-jlzs--implement-gitea-actions-cicd-pipeline.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-jlzs title: Implement Gitea Actions CI/CD pipeline -status: in-progress +status: completed type: task priority: normal created_at: 2026-02-10T09:38:15Z -updated_at: 2026-02-10T11:12:32Z +updated_at: 2026-02-10T11:34:52Z parent: nuzlocke-tracker-ahza --- @@ -24,6 +24,6 @@ Set up Gitea Actions as the CI/CD pipeline for the nuzlocke-tracker. Gitea Actio - [x] **Set up a Gitea Actions runner** — `act_runner` is deployed on Unraid and registered with Gitea - [x] **Create CI workflow** (`.github/workflows/ci.yml`) — on push to `develop` and PRs: run `ruff check` + `ruff format --check` for backend, `eslint` + `tsc` for frontend. Tests can be added later when they exist. - [x] **Create deploy workflow** (`.github/workflows/deploy.yml`) — triggered via `workflow_dispatch` on `main`: build Docker images (linux/amd64), push to the Gitea container registry, deploy to Unraid via SSH (`docker compose pull && docker compose up -d`) -- [ ] **Configure secrets in Gitea** — generate a new SSH keypair, add the public key to Unraid root user's `authorized_keys`, add the private key as a Gitea repo secret (`DEPLOY_SSH_KEY`). Also add any registry credentials or other sensitive values the workflows need. -- [ ] **Test the full pipeline** — push a change through `feature/*` → `develop` (verify CI runs), then merge `develop` → `main` and trigger the deploy workflow via `workflow_dispatch` to verify end-to-end -- [ ] **Update deployment docs** — document the Gitea Actions setup, how to manage the runner, and how CI/CD fits into the deployment workflow \ No newline at end of file +- [x] **Configure secrets in Gitea** — generate a new SSH keypair, add the public key to Unraid root user's `authorized_keys`, add the private key as a Gitea repo secret (`DEPLOY_SSH_KEY`). Also add any registry credentials or other sensitive values the workflows need. +- [x] **Test the full pipeline** — push a change through `feature/*` → `develop` (verify CI runs), then merge `develop` → `main` and trigger the deploy workflow via `workflow_dispatch` to verify end-to-end +- [x] **Update deployment docs** — document the Gitea Actions setup, how to manage the runner, and how CI/CD fits into the deployment workflow \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md b/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md index 784999f..72ba486 100644 --- a/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md +++ b/.beans/nuzlocke-tracker-re0m--document-the-deployment-workflow.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-re0m title: Document the deployment workflow -status: in-progress +status: completed type: task priority: normal created_at: 2026-02-09T15:30:57Z -updated_at: 2026-02-10T08:44:29Z +updated_at: 2026-02-10T11:35:41Z parent: nuzlocke-tracker-ahza blocking: - nuzlocke-tracker-aiw6 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f59f746..5ce6a35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,10 +5,18 @@ on: branches: [develop] paths-ignore: - ".beans/**" + - "*.md" + - "LICENSE" + - ".gitignore" + - ".github/workflows/deploy.yml" pull_request: branches: [develop] paths-ignore: - ".beans/**" + - "*.md" + - "LICENSE" + - ".gitignore" + - ".github/workflows/deploy.yml" jobs: backend-lint: diff --git a/CLAUDE.md b/CLAUDE.md index c3d2249..28ab3bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,8 @@ - Day-to-day work happens on `develop`. - New work is done on `feature/*` branches off `develop`. - Merge flow: `feature/*` → `develop` → `main`. +- **Squash merge** `feature/*` into `develop` (one clean commit per feature). +- **Merge commit** `develop` into `main` (marks deploy points). # Instructions diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 236d6a6..da81873 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,31 +1,95 @@ # Deployment -This document describes the deployment architecture and workflows for the nuzlocke-tracker. It is a living document — sections marked with **TODO** are planned but not yet implemented. +This document describes the deployment architecture and workflows for the nuzlocke-tracker. ## Architecture Overview | Component | Dev (Laptop/PC) | Production (Unraid) | |---|---|---| | API | `docker-compose.yml` (hot reload) | `docker-compose.prod.yml` (built image) | -| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (built image) | -| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (Docker volume) | +| Frontend | `docker-compose.yml` (Vite dev server) | `docker-compose.prod.yml` (nginx, static build) | +| Database | PostgreSQL 16 (Docker volume) | PostgreSQL 16 (bind mount) | | Container Registry | — | Gitea (user-level packages) | -| Container Management | — | Portainer CE | | Reverse Proxy | — | Nginx Proxy Manager | +| CI/CD | — | Gitea Actions | ### Services -- **Gitea** — self-hosted Git server, container registry, and (future) CI/CD. Accessible at `gitea.nerdboden.de` via SSL. -- **Portainer** — Docker management UI. Accessible at `portainer.nerdboden.de` via SSL. Manages the production stack and provides webhook-triggered redeployments. +- **Gitea** — self-hosted Git server, container registry, and CI/CD runner. Accessible at `gitea.nerdboden.de` via SSL. - **Nginx Proxy Manager** — reverse proxy with SSL termination for all services on the Unraid server. +## Branching Strategy + +| 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. | — | + +### Workflow + +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 + +## CI/CD (Gitea Actions) + +The project uses two Gitea Actions workflows (GitHub Actions-compatible syntax): + +### CI (`.github/workflows/ci.yml`) + +**Triggers:** Push to `develop`, PRs targeting `develop` (skips bean-only changes) + +**Jobs:** +- `backend-lint` — `ruff check` + `ruff format --check` +- `frontend-lint` — `eslint` + `tsc -b` + +### Deploy (`.github/workflows/deploy.yml`) + +**Triggers:** Manual via `workflow_dispatch` (must be on `main`) + +**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` + +### Secrets (configured in Gitea repo settings) + +| Secret | Purpose | +|---|---| +| `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` | + +### Runner + +An `act_runner` container runs on Unraid with the Docker socket mounted, registered with the Gitea instance. + +## Manual Deployment + +The `./deploy.sh` script can also be used to deploy from the dev machine: + +```bash +# Ensure you're on main with latest changes +git checkout main +./deploy.sh +``` + +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 + ## Container Registry -Docker images are hosted on Gitea's built-in container registry as **user-level packages**. - -### Image naming - -Images use the format `gitea.nerdboden.de//:`: +Images are hosted on Gitea's built-in container registry as user-level packages: ``` gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest @@ -35,118 +99,58 @@ gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ### Authentication 1. Create a Gitea access token at **Settings > Applications** with `read:package` and `write:package` scopes. -2. Log in from the dev machine: - ```bash - docker login gitea.nerdboden.de - ``` - Use your Gitea username and the access token as password. +2. Log in: `docker login gitea.nerdboden.de` (username + token as password). -### Pushing images +## Production Environment -```bash -# Build and tag -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend +### File layout on Unraid -# Push -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest +``` +/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) ``` -Pushed images are visible under the **Packages** tab on your Gitea user profile. +### Environment variables -## Branching Strategy - -The project uses a `main` / `develop` / `feature/*` branching model. - -| Branch | Purpose | -|---|---| -| `main` | Always production-ready. Deploy script builds from here. | -| `develop` | Integration branch for day-to-day work. | -| `feature/*` | Short-lived branches off `develop` for individual features/fixes. | - -### Workflow - -1. Create `feature/xyz` from `develop` -2. Work on the feature, commit, merge into `develop` -3. When ready to deploy: merge `develop` into `main` -4. Run the deploy script (see below) - -## Deploying - -> **TODO** — deploy script (`./deploy.sh`) not yet created. - -The deploy script will automate: - -1. Build Docker images from `main` -2. Tag and push to the Gitea container registry -3. Trigger the Portainer webhook to pull new images and restart the stack - -### Manual deployment - -Until the deploy script is in place, deploy manually: - -```bash -# 1. Ensure you're on main with latest changes -git checkout main - -# 2. Build and push images -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest ./backend -docker build -t gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest ./frontend -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest -docker push gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest - -# 3. On Unraid (or via Portainer): pull and restart -docker compose -f docker-compose.prod.yml pull -docker compose -f docker-compose.prod.yml up -d -``` - -## Production Compose - -> **TODO** — `docker-compose.prod.yml` not yet created. - -The production compose file will differ from the dev compose in: - -- Uses pre-built images from the Gitea registry (no source volume mounts) -- No hot reload / debug mode -- Production environment variables -- Proper restart policies -- Frontend served as a static build (not Vite dev server) - -## Portainer - -Portainer CE is running on Unraid at `portainer.nerdboden.de`. - -- Manages the production Docker stack -- **TODO**: Configure a webhook for automated redeployment (pull latest images + restart on trigger) - -## Nginx Proxy Manager - -NPM runs on Unraid and handles SSL termination and routing for: - -- `gitea.nerdboden.de` → Gitea -- `portainer.nerdboden.de` → Portainer -- **TODO**: `nuzlocke.nerdboden.de` (or similar) → nuzlocke-tracker frontend/API - -## Environment & Secrets - -> **TODO** — `.env.prod` template not yet created. - -Production environment variables to configure: - -- `DATABASE_URL` — PostgreSQL connection string -- `DEBUG` — must be `false` in production -- Additional secrets TBD +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. ## Database -PostgreSQL 16 with data stored in a named Docker volume. +PostgreSQL 16 with data persisted via bind mount at `./data/postgres/`. -- Migrations run automatically on API container startup (Alembic) -- **TODO**: Set up scheduled backups (`pg_dump` cron job on Unraid) +- 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) + +### Backup + +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. + +### Restore + +```bash +cd /mnt/user/appdata/nuzlocke-tracker +gunzip -c backups/nuzlocke-YYYYMMDD-HHMMSS.sql.gz | \ + docker compose exec -T db psql -U postgres nuzlocke +``` ## Rollback -> **TODO** — rollback procedure to be documented once image tagging strategy is finalized. +Currently images are tagged as `latest` only. To roll back: -General approach: tag images with version/commit hash in addition to `latest`, so rolling back means redeploying a previous tag. +1. Revert the merge commit on `main` +2. Trigger the deploy workflow (or run `./deploy.sh`) to rebuild and push + +For more granular rollback, consider adding commit-hash tags to images in the future. + +## Nginx Proxy Manager + +NPM runs on Unraid and handles SSL termination and routing: + +- `nuzlocke.nerdboden.de` → nuzlocke-tracker frontend (port 9080) +- `gitea.nerdboden.de` → Gitea