# Deploy to Synology NAS (Container Manager) — LAN / HTTP Target: run the ROLAC stack on a Synology **DS220+** (Celeron J4025 / 2GB RAM), reachable on the LAN at `http://:8080`. Images are **built on the dev PC** (the NAS is too weak to compile — Angular's build alone can need >2GB RAM), pushed to the **Gitea registry** on the NAS, and the NAS only **pulls + restarts** the containers. ``` push main dev PC ───────────────► Gitea (NAS) (runner: builder) │ triggers .gitea/workflows/ci-cd-nas.yml test + build + push ─────────┤ ▼ NAS runner (label: nas) ── deploy only ──┐ ▼ browser (LAN) -> http://:8080 │ nginx edge (container, 8080->80) ├── / -> app container (Angular static) └── /api/ -> api container (ASP.NET, :8080) api ──> existing PostgreSQL @ 192.168.68.55:49154 (not containerized) ``` Why this split: DS220+ can comfortably **run** these lightweight containers (nginx + precompiled .NET + static files) but cannot **build** them. So building (test, `dotnet publish`, `ng build`) runs on the dev PC; the NAS just pulls. Differences vs the Azure plan: no TLS/certbot, edge on **8080** (DSM owns 80/443), reuse the LAN database, deploy via the on-NAS runner (no SSH). --- ## Two runners, two jobs | Job | `runs-on` | Where | Does | |-----|-----------|-------|------| | `build-push` | `windows` | **dev PC** | test → build both images → push to registry | | `deploy` | `nas` | **NAS** | pull images → `docker compose up -d` → health check | > The dev-PC runner is registered with the label `windows:host` — `runs-on` matches > the label NAME (`windows`); `:host` is the run mode (executes directly on the PC, > not in a container, so it uses Docker Desktop + the installed .NET SDK). `deploy` has `needs: build-push`, so it only runs after the build succeeds. --- ## One-time setup — DEV PC (the `windows` runner) ✅ already done The dev PC runs act_runner natively with the label `windows:host`, using its installed Docker Desktop + .NET 8 SDK. The workflow's `build-push` job targets `runs-on: windows`. Requirements (for reference): - Docker Desktop running, and `docker` on PATH. - .NET 8 SDK on PATH (`dotnet test` runs on this machine). - **Git for Windows** installed — the job uses `shell: bash` (Git Bash) for the multi-line `docker build`/`push` steps. ## One-time setup — NAS (the `nas` runner) 1. **Deploy dir + secrets** (via SSH or File Station): ```bash mkdir -p /volume1/docker/rolac/nginx/conf.d /volume1/docker/rolac/data/api-storage cp /path/to/repo/deploy/nas/.env.example /volume1/docker/rolac/.env # edit /volume1/docker/rolac/.env -> real DB user/password + JWT_SECRET + APP_ORIGIN ``` 2. **Registry token** — in Gitea: Settings → Applications → new token with `read:package` + `write:package`. Log the NAS Docker in once: ```bash docker login git.golife.love -u ChrisChen # paste the token ``` 3. **Install act_runner on the NAS** (Container Manager → Registry → `gitea/act_runner`, or `docker run`). It must: - mount the host Docker socket: `-v /var/run/docker.sock:/var/run/docker.sock` - mount the deploy dir at the same path: `-v /volume1/docker/rolac:/volume1/docker/rolac` - register with the label **`nas`** (this is what `runs-on: nas` targets). ```bash docker run -d --restart unless-stopped --name rolac-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /volume1/docker/rolac:/volume1/docker/rolac \ -e GITEA_INSTANCE_URL=https://git.golife.love \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ -e GITEA_RUNNER_LABELS=nas \ gitea/act_runner:latest ``` ## One-time setup — Gitea repo 1. **Secrets** (Settings → Actions → Secrets): - `REGISTRY_USER` = `ChrisChen` - `REGISTRY_TOKEN` = the package token (with `write:package`) 2. **Enable Actions** for the repo if not already (Settings → Advanced → Actions). --- ## Day-to-day `git push` to `main` → `.gitea/workflows/ci-cd-nas.yml`: 1. **dev PC** (`builder`): `dotnet test` → build `rolac-api` + `rolac-app` (tags `:latest` and `:`) → push to `git.golife.love/chrischen/*`. 2. **NAS** (`nas`): sync compose/nginx → `TAG= docker compose pull` → `docker compose up -d` → `curl /api/health`. Open `http://:8080` and log in. Deploy pins `TAG=` (not `latest`), so the NAS always runs exactly the image this commit produced and `compose pull` forces a fresh fetch. --- ## Manual fallback (no runners yet) From the dev PC (Docker Desktop + `docker login git.golife.love`): ```powershell # repo root — build + push both images (tags :latest and :) .\deploy\build-push.ps1 ``` Then on the NAS: ```bash cd /volume1/docker/rolac docker compose pull docker compose up -d curl -fsS http://localhost:8080/api/health ``` --- ## Notes - **First boot runs DB migrations** against `192.168.68.55` automatically (`Program.cs` calls `MigrateAsync()` + seed). Make sure the DB user has DDL rights; back up before the first run. - **Bind-mount paths**: the NAS runner runs compose at `/volume1/docker/rolac` on the host (socket-mounted), so `./nginx/conf.d` and `./data` resolve to real NAS paths — that's why the runner mounts that dir at the *same* path. - **Uploaded files** persist under `/volume1/docker/rolac/data/api-storage`. - **DS220+ runs, never builds.** Keep all compilation on the dev PC / a beefier runner. - To expose beyond the LAN later, put it behind DSM's reverse proxy (Application Portal) or switch to the Azure `deploy/` files with certbot.