- Build linux/amd64 and linux/arm64 on native GitHub runners - Assemble version manifests from platform digests and promote latest only after verification - Add release/tag validation, manual republishing, timeouts, and health smoke tests - Make Docker build mirrors configurable via build args and remove unnecessary runtime apk upgrades - Update DOCKER.md documentation
6.1 KiB
Docker
Run 9Router in a container. Published image: decolua/9router — multi-platform linux/amd64 + linux/arm64.
👤 For Users
Quick start
docker run -d \
-p 20128:20128 \
-v "$HOME/.9router:/app/data" \
-e DATA_DIR=/app/data \
--name 9router \
decolua/9router:latest
App listens on port 20128. Open: http://localhost:20128
Manage container
docker logs -f 9router # view logs
docker stop 9router # stop
docker start 9router # start again
docker rm -f 9router # remove
Data persistence
-v "$HOME/.9router:/app/data" \
-e DATA_DIR=/app/data
Without DATA_DIR, the app falls back to ~/.9router/ (macOS/Linux) or %APPDATA%\9router\ (Windows). In the container, DATA_DIR=/app/data makes the bind mount work.
Data layout under $DATA_DIR/:
$DATA_DIR/
├── db/
│ ├── data.sqlite # main SQLite database
│ └── backups/ # auto backups
└── ... # certs, logs, runtime configs
Host path: $HOME/.9router/db/data.sqlite
Container path: /app/data/db/data.sqlite
Optional env vars
docker run -d \
-p 20128:20128 \
-v "$HOME/.9router:/app/data" \
-e DATA_DIR=/app/data \
-e PORT=20128 \
-e HOSTNAME=0.0.0.0 \
-e DEBUG=true \
--name 9router \
decolua/9router:latest
Optional Headroom sidecar
The 9Router image does not bundle Python or Headroom. To use Headroom in Docker, run it as a separate service and point 9Router at that proxy:
services:
9router:
image: decolua/9router:latest
ports:
- "20128:20128"
volumes:
- "$HOME/.9router:/app/data"
environment:
DATA_DIR: /app/data
HEADROOM_URL: http://headroom:8787
depends_on:
- headroom
headroom:
image: ghcr.io/chopratejas/headroom:latest
ports:
- "8787:8787"
In the dashboard, open Endpoint → Token Saver → Headroom, confirm the URL is http://headroom:8787, recheck status, then enable Headroom.
If Headroom runs on the Docker host instead of as a sidecar, use http://host.docker.internal:8787 on macOS/Windows. On Linux, add --add-host=host.docker.internal:host-gateway or the equivalent compose extra_hosts entry.
Update to latest
docker pull decolua/9router:latest
docker rm -f 9router
# re-run the quick start command
To pin a specific version instead of following latest, use a numbered image tag:
docker pull decolua/9router:0.5.81
🛠 For Developers
Build image locally (test)
docker build -t 9router .
docker run --rm -p 20128:20128 \
-v "$HOME/.9router:/app/data" \
-e DATA_DIR=/app/data \
9router
The Dockerfile uses the official Alpine and npm registries by default. Regional mirrors can be supplied when needed:
docker build \
--build-arg ALPINE_MIRROR=mirrors.aliyun.com \
--build-arg NPM_REGISTRY=https://registry.npmmirror.com/ \
-t 9router .
Publish (automatic via CI)
Push a Docker-safe semver git tag vX.Y.Z (or a prerelease such as vX.Y.Z-rc.1) → GitHub Actions builds linux/amd64 and linux/arm64 on native runners, health-checks each platform image, verifies the resulting manifest and /api/health, then publishes:
ghcr.io/decolua/9router:X.Y.Z+:latestdecolua/9router:X.Y.Z+:latest
The v prefix is used only for the git tag; image tags omit it. A stable tag push promotes latest, but a prerelease tag such as vX.Y.Z-rc.1 publishes only its numbered image by default. Prereleases require an explicit manual promote_latest opt-in. Promotion happens only after both native platform builds, both platform health checks, manifest inspection, and the resolved-manifest smoke test succeed. A failed or timed-out platform build therefore cannot move latest.
The workflow rejects SemVer build metadata such as v1.2.3+build.7 because the + form is not a valid Docker image tag. The git tag and both package.json versions must match exactly.
# Use scripts/release.js (recommended)
node scripts/release.js "Release title" "Notes"
# Or manually
git tag v0.5.81 && git push origin v0.5.81
To republish an existing tag, run the Build and Push Docker Image workflow manually and provide the exact tag, for example v0.5.81, in the release_tag input. Manual runs publish the numbered tag but leave latest unchanged by default:
release_tag: v0.5.81
promote_latest: false
The promote_latest checkbox is an explicit opt-in for changing latest. Use it when a deliberate rollback or recovery should make that version the current default:
release_tag: v0.5.75
promote_latest: true
Numbered image tags are mutable because a republish can replace their manifest. For a deployment that must be immutable, pin the image digest instead:
docker pull decolua/9router@sha256:<verified-digest>
The release workflow runs /api/health on each native amd64 and arm64 platform image before it uploads the digest artifact or assembles the multi-platform manifest. It then runs a second health check against the resolved version manifest before any requested latest promotion.
During recovery, the selected tag remains the application source while the Dockerfile from the workflow revision is used, so an older tag can be rebuilt with the current publishing fixes.
The workflow is tag-driven. Creating a git tag does not automatically create a GitHub Release, so the Releases page and the published package/image tags can be at different versions unless a maintainer creates a release separately.
The upstream repository needs these repository secrets for Docker Hub publishing:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN
GHCR publishing uses the workflow's GITHUB_TOKEN with package write permission. Forks can publish to their own GHCR namespace, but Docker Hub publication is restricted to the upstream decolua/9router repository.
The optional repository variables ALPINE_MIRROR and NPM_REGISTRY can override the default package mirrors used by the CI Docker build.
Workflow: .github/workflows/docker-publish.yml