# Deployment How a new version of the website ends up on Unraid. Current state: **manual** — build the image locally, push it to the Gitea registry, pull it on Unraid. Automation via Gitea Actions comes later. > **Test vs. production.** As long as the domain has not moved yet and NPM is not > in front of it, the container runs with a port mapping and is directly reachable > on the local network (`http://:5000`). In production the mapping is gone > — then NPM is the only path to the container. The two compose variants are > described separately below. --- ## Prerequisites (one-time) ### Gitea registry token The push to the registry needs a Gitea access token with **package write permission** (`package: Read and Write`) — **not** the account password. 1. Gitea → top right profile picture → **Settings** → **Applications**. 2. **Manage Access Tokens** section: assign a name (e.g. `registry-push`). 3. Under **Select scopes**: set `package` to **Read and Write**. 4. Click **Generate Token**, **copy the string immediately** (shown only once). > **The token is a secret.** Never store it in plain text in Git, chats, > screenshots, or tickets. If one does become visible: **delete** it in Gitea and > generate a new one. A `package` token allows uploading arbitrary images to the > registry. The Docker login then stores the token locally, so it only has to be entered once. --- ## Way of working: branches, never directly on main `main` is the **published** state — only `:latest` is built from it, and only `:latest` is pulled by Unraid. That is why we never commit directly to `main`: 1. Create a feature branch: `git switch -c /` (e.g. `deployment/sha-tagging`, `content/pipeline`). 2. Commit there, push the branch: `git push -u origin `. 3. Open a **Pull Request** against `main` in Gitea and merge it there. 4. Only then build the release image from `main` (below). **Dev images stay local.** For trying things out on your own machine: ```bash scripts/dev-build.sh # builds elternbeirat-web:dev-, pushes NOTHING ``` This way a development state can never accidentally land in the registry as `:latest`. ## Rolling out a new version (manual) Prerequisite: logged in to the registry once (see below). Then, **on main** and with a clean working directory: ```bash scripts/release.sh ``` The script builds the image, tags it with `:latest` **and** the short commit SHA (for rollback), and pushes both. It **aborts** if you are not on `main` or have uncommitted changes, and warns about unpushed commits. > The image path `gitea.anticarnist.de/tom/elternbeirat` is **lowercase** — > container registries require that in the path, even though the user (`Tom`) and > the repo (`Elternbeirat`) are capitalized. ### Logging Unraid in to the registry once The image is **private**, so Unraid has to log in once before it can pull. The **Compose Manager Plus** plugin has no UI field for this — the login runs through the Unraid terminal (`>_` symbol at the top right of the web interface, prompt `root@Cube:~#`): ```bash docker login gitea.anticarnist.de # Username: Tom # Password: ``` The login stays stored; it only has to be repeated when the token changes. Two pitfalls learned from experience: - **Don't confuse it with PowerShell/laptop.** The login has to happen in the *Unraid* terminal (`root@Cube`), not in Windows PowerShell (`PS C:\`). The laptop needs the login only to *push*, Unraid to *pull*. - **A wrong username gets stuck.** If the login reports "Stored credentials invalid or expired" and does *not* ask for the name, first `docker logout gitea.anticarnist.de`, then log in again — otherwise a nonsense username gets stored by accident. - **Plain-text warning.** Docker stores the token unencrypted in `/root/.docker/config.json`. On your own server, okay for a start. *Cleaner later:* set up a credential helper (→ open item below). ### Pull and start again on Unraid In the **Compose Manager Plus** plugin (Unraid web interface): - **Docker** tab → **Compose** section → stack **elternbeirat** → **Compose Down**, then **Compose Up** (or "Pull" + "Up", depending on the plugin version), so that the new `:latest` is pulled. > `docker compose up` does **not** automatically re-pull a `:latest` if an image of > the same name is already present locally. When in doubt, explicitly "Pull" first. --- ## compose variants ### Test (now: without NPM, directly reachable on the LAN) Present in the repo as `compose.yaml`. Pulls the registry image and maps port **5000 → 8080**: ```yaml services: eb-web: image: gitea.anticarnist.de/tom/elternbeirat:latest container_name: eb-web restart: unless-stopped environment: ASPNETCORE_URLS: http://+:8080 TZ: Europe/Berlin ports: - "5000:8080" # TEST access, remove in production ``` Reachable at `http://:5000`. ### Production (later: only via NPM) No `ports:` block, instead the external NPM Docker network: ```yaml services: eb-web: image: gitea.anticarnist.de/tom/elternbeirat:latest container_name: eb-web restart: unless-stopped environment: ASPNETCORE_URLS: http://+:8080 TZ: Europe/Berlin networks: [npm] networks: npm: external: true ``` --- ## Rollback `scripts/release.sh` additionally tags each release with the short commit SHA, which — unlike the moving `:latest` — never shifts. To roll back, replace `:latest` in the Unraid `compose.yaml` with the `:` of the last working state and bring it back up: ```yaml image: gitea.anticarnist.de/tom/elternbeirat:99dd052 # statt :latest ``` Which SHA tags are in the registry is shown by Gitea under `Tom/-/packages` → `elternbeirat`. --- ## Automation ### Build and push (done) `.gitea/workflows/deploy.yml` builds the image on every push to `main` and pushes it to the registry, tagged `:latest` **and** `:` — the same scheme as `scripts/release.sh`, just run by the `act_runner` instead of by hand. It does **not** deploy; pulling and starting the new `:latest` on Unraid stays a manual, deliberate step (see "Pull and start again on Unraid" above). Requires two repository secrets (Gitea → repo → **Settings** → **Actions** → **Secrets**): - `REGISTRY_USER` — the registry username (e.g. `Tom`). - `REGISTRY_TOKEN` — a Gitea access token with **`package: write`**, *not* the login password. Two known pitfalls: the `act_runner` needs Docker socket access to build, and it must offer the `ubuntu-latest` label the workflow asks for. To check the runner: Gitea → repo (or site admin) → **Settings** → **Actions** → **Runners** — it should be listed as **online** with a label set that includes `ubuntu-latest`. ### Auto-deploy via Watchtower (open — needs a health gate first) Automatically rolling out `:latest` the moment it lands in the registry is tempting, but **not** something to switch on blindly: a build can be green and still serve a broken page (a bad content file, a runtime-only culture crash like the de-DE one). Auto-deploy without a gate would push that live **unnoticed**. So before turning this on, decide the gate: - The container must prove itself **healthy** before it replaces the running one — but the chiseled image has no shell, so a `HEALTHCHECK` with `curl`/`sh` does not work inside it. The check has to come from outside (e.g. an external probe hitting a known route, or a compose-level check from a sidecar). - Watchtower must be **label-scoped** to *only* the `eb-web` container, otherwise it updates the entire home-lab inventory. - Keep a fast **rollback**: pin `:` in the Unraid compose and bring it back up (see "Rollback" above). Until that gate exists, deployment stays manual on purpose. ### Encrypt the registry token on Unraid (open) Currently the token sits in plain text in `/root/.docker/config.json`. Set up a credential helper later, so that the plain-text warning from `docker login` disappears.