UGREEN NAS Stack
Consolidated compose stack for the UGREEN NAS (UGREEN-NAS-MKP, 192.168.1.29, user
unknown224).
Migrated from twelve independent compose projects that lived in
/volume1/docker-ssd/<app>/ and /volume2/docker/<app>/, each with its own
docker-compose.yaml and hardcoded secrets.
Layout
Section titled “Layout”| Path | Contents |
|---|---|
docker-compose.yml | include: list — the only file compose is invoked on |
host/arcane.yaml | Arcane agent, extends ../../arcane/arcane-agent.yaml |
host/deunhealth.yaml | Watchdog restarting unhealthy gluetun-namespace sharers |
apps/<name>/<name>.yaml | One file per application |
dozzle-agent comes from the shared ../dozzle/dozzle-agent.yaml.
Services
Section titled “Services”| Service | Port(s) | Data location |
|---|---|---|
gluetun | 21287, 21265, 6881-6882, 21478 | /home/unknown224/Apps/gluetun |
gluetun-webui | 65258 | — |
qbittorrent1 | via gluetun (21287) | /volume1/docker-ssd/qbittorrent/appdata |
rustatio | via gluetun (21478) | volume rustatio_data |
qbitwebui | 26610 | /volume1/docker-ssd/qbitwebui/data |
qui | 7476 | /volume1/docker-ssd/qui-bittorrent/qui |
anibridge | 4848 | /volume1/docker-ssd/anibridge/data |
metube | 8081 | /volume2/metube/{downloads,audio-downloads} |
speedtest-tracker | 8899, 8793 | /volume1/docker-ssd/speedtest-tracker/config |
upsnap | host network | /volume1/docker-ssd/upsnap/data |
dozzle-agent | 7007 | — |
arcane-agent | 3553 | volume arcane-agent-data |
deunhealth | none | — |
backup2 | none | /volume2/docker-apps-backup (archives) |
Registry
Section titled “Registry”Every image is pulled through the internal Harbor at harbor.toolsera.lan, using the
proxy-cache project that matches its upstream registry:
| Upstream | Harbor project |
|---|---|
| Docker Hub | docker_proxy_cache |
ghcr.io | ghcr_proxy_cache |
lscr.io | lscr_proxy_cache |
The registry host is stripped and the rest of the path is preserved, e.g.
ghcr.io/autobrr/qui:latest becomes
harbor.toolsera.lan/ghcr_proxy_cache/autobrr/qui:latest.
Harbor requires authentication (/v2/ returns 401), which the Docker daemon holds as
root. Note that docker manifest inspect run as unknown224 fails with
open /etc/docker/certs.d/harbor.toolsera.lan/ca.crt: permission denied — that is a
client-side file-permission issue, not a registry problem. Pulls performed by the daemon
are unaffected.
What changed during migration
Section titled “What changed during migration”- Secrets externalised. Every hardcoded credential became a
${VAR}placeholder documented in.env.example. Nothing in this directory carries a real value. - Relative bind mounts made absolute.
./dataand./quiresolved against each app’s old directory; consolidating the compose files into one project would have silently repointed them at new empty directories. They now name the existing paths explicitly, so state is preserved. network_mode: service:gluetunonqbittorrentandrustatio, gated behinddepends_on: condition: service_healthy, with orphan-detecting healthchecks and adeunhealthwatchdog. Seeapps/qbittorrent/README.md— a gluetun restart silently strands both containers in a dead network namespace.container_nameadded toanibridgeandqbitwebui, which previously ran asanibridge-anibridge-1/qbitwebui-qbitwebui-1.version:keys dropped fromquiandupsnap(obsolete in compose v2).- All images repointed at Harbor. See Registry.
dozzle-agentcaptured as a compose service via the shared../dozzle/dozzle-agent.yaml; it was a baredocker runcontainer.- gluetun’s control-server auth JSON fixed. The live container has
HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE='{"auth":...}'— the single quotes are literal, so the value is not parseable JSON and gluetun falls back to/gluetun/auth/config.toml. This stack passes valid JSON, which means apikey auth actually takes effect on the control server.gluetun-webuialready sends the key, but anything else polling:8000will start getting 401s.
Not migrated
Section titled “Not migrated”firefox lives at /volume2/@appstore/com.ugreen.docker.firefox/ and is managed by the
UGREEN app store, which owns its lifecycle and rewrites its compose file. Bringing it
into this stack would fight the appliance. Left in place.
watchtower and portainer_agent are running on the NAS but are deliberately not part
of this stack. They stay under whatever manages them today.
Before the first up -d
Section titled “Before the first up -d”- Rotate every secret. The old compose files on the NAS committed the Arcane agent
token, CyberGhost OpenVPN credentials, gluetun API key, qbitwebui encryption key and
the speedtest-tracker
APP_KEYin plaintext on disk. - Confirm the Arcane manager URL. The shared
arcane/arcane-agent.yamlpoints athttp://192.168.1.175:3552; the NAS agent is currently pointed athttp://192.168.1.178:3552. One of the two is stale. - Named volumes get renamed by the project switch.
arcane-agent_arcane-dataandrustatio_rustatio_databecomeugreen-nas_arcane-agent-dataandugreen-nas_rustatio_data. Both start empty — the Arcane agent will need re-pairing, and rustatio (currently exited) loses its/data. Copy the old volumes across first if that state matters. - Stop the old projects first. Container names collide, so the twelve existing projects must be down before this one comes up.
Running
Section titled “Running”cd /volume1/docker-ssd/infrastructure-toolkit/docker-compose-apps/ugreen-nasbws run --access-token "$BWS_ACCESS_TOKEN" -- docker compose up -dFootnotes
Section titled “Footnotes”-
https://github.com/markkpamy/infrastructure-toolkit/blob/main/docker-compose-apps/ugreen-nas/Pinned to an exact build and upgraded one-way — back up
appdatabefore any version bump. Seeapps/qbittorrent/README.md. ↩ -
https://github.com/markkpamy/infrastructure-toolkit/blob/main/docker-compose-apps/ugreen-nas/Nightly config/state backup of every app above. Adding an app to this stack means adding its data path to
apps/backup/backup.yaml— coverage is not automatic. Seeapps/backup/README.mdfor the restore procedure and the encryption-key constraint. ↩