7.9 KiB
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://<unraid>: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.
- Gitea → top right profile picture → Settings → Applications.
- Manage Access Tokens section: assign a name (e.g.
registry-push). - Under Select scopes: set
packageto Read and Write. - 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
packagetoken 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:
- Create a feature branch:
git switch -c <area>/<short>(e.g.deployment/sha-tagging,content/pipeline). - Commit there, push the branch:
git push -u origin <branch>. - Open a Pull Request against
mainin Gitea and merge it there. - Only then build the release image from
main(below).
Dev images stay local. For trying things out on your own machine:
scripts/dev-build.sh # builds elternbeirat-web:dev-<branch>, 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:
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/elternbeiratis 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:~#):
docker login gitea.anticarnist.de
# Username: Tom
# Password: <package token>
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
:latestis pulled.
docker compose updoes not automatically re-pull a:latestif 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:
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://<unraid>:5000.
Production (later: only via NPM)
No ports: block, instead the external NPM Docker network:
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 :<sha> of the last working state
and bring it back up:
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 :<short-sha> — 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 withpackage: 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
HEALTHCHECKwithcurl/shdoes 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-webcontainer, otherwise it updates the entire home-lab inventory. - Keep a fast rollback: pin
:<sha>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.