Skip to content

Add Docker and Docker Compose support#7

Merged
lkosewsk merged 6 commits into
tailscale:mainfrom
Veslydev:docker-support
Jun 12, 2026
Merged

Add Docker and Docker Compose support#7
lkosewsk merged 6 commits into
tailscale:mainfrom
Veslydev:docker-support

Conversation

@Veslydev

Copy link
Copy Markdown
Contributor

Summary

Adds Docker and Docker Compose support so deploying and updating tsheadroom no longer requires manual Go toolchain setup, venv management, or systemd unit editing on the host.

  • Dockerfile — multi-stage build (Go 1.26 compiler → python:3.13-slim runtime). A HEADROOM_VARIANT build arg selects between headroom-ai (base, tool outputs only, low memory) and headroom-ai[ml] (full text + tool compression, ~600 MB ML model per worker). Python 3.13 is pinned because headroom-ai ships prebuilt wheels for it — no Rust toolchain needed.
  • docker-compose.yml — single service backed by two named volumes and one bind mount:
    • tsheadroom-state (named) — tailnet device identity; persists across restarts so the node never re-authenticates.
    • tsheadroom-hf-cache (named) — HuggingFace model cache, shared by all workers in the container; survives down/up so the ~600 MB model is never re-downloaded.
    • ./config/etc/tsheadroom (bind) — the live compression config (config.json). A directory, not a single file, so the binary's atomic PUT /config write can rename within it (a single-file bind mount fails this with EBUSY). Edit config/config.json on the host and restart, or change it live via PUT /config; both paths are verified to persist.
    • Operator knobs (TS_AUTHKEY, TS_HOSTNAME, HEADROOM_VARIANT, POOL_SIZE) live in .env.
  • .env.example — copy to .env and fill in to get started.
  • tsheadroom.config.example.json — committed default compression config (mirrors the binary's built-in defaults). Copy it to config/config.json before first start, the same way you copy .env.example.
  • .dockerignore — excludes build/, .git/, tests/, docs/, .env from the build context.

Deploy

cp .env.example .env
# edit .env: set TS_AUTHKEY, choose HEADROOM_VARIANT (base or ml), set POOL_SIZE
# Tip: use POOL_SIZE=1 on a fresh host to let the ML model download first, then raise it

mkdir -p config && cp tsheadroom.config.example.json config/config.json
# (optional) edit config/config.json to tune compression knobs

docker compose up -d --build

Update

git pull && docker compose up -d --build

--build recompiles the Go binary and reinstalls Python deps if anything changed. The tailnet identity and model cache are preserved in volumes.

Migrate from systemd

# 1. Stop and disable the existing unit
sudo systemctl stop tsheadroom && sudo systemctl disable tsheadroom

# 2. Copy tailnet state into the Docker volume (skips re-authentication)
docker run --rm \
  -v tsheadroom-state:/dst \
  -v /var/lib/tsheadroom:/src:ro \
  alpine sh -c "cp -a /src/. /dst/"

# 3. Copy HF model cache if present (skips re-download of ~600 MB)
docker run --rm \
  -v tsheadroom-hf-cache:/dst \
  -v "$HOME/.cache/huggingface:/src:ro" \
  alpine sh -c "cp -a /src/. /dst/"

# 4. Carry over your tuned compression config, or start from the example
mkdir -p config
cp /var/lib/tsheadroom/tsheadroom.config.json config/config.json 2>/dev/null \
  || cp tsheadroom.config.example.json config/config.json

# 5. Start
docker compose up -d --build

Testing

  • Built both variants locally: base (630 MB) and ml (CUDA stack pulled in by torch).
  • ml image: verified headroom + ML deps import inside the image.
  • Ran the container on -local-addr: a short chat returns {"action":"allow"}.
  • Verified the ./config bind mount round-trips: GET /config reads it and PUT /config persists changes back to the host file.

Veslydev added 5 commits June 12, 2026 09:16
Signed-off-by: Veslydev <vesly@vesly.dev>
Signed-off-by: Veslydev <vesly@vesly.dev>
Signed-off-by: Veslydev <vesly@vesly.dev>
Bind-mount a config directory (not a single file) so the binary's atomic
PUT /config write can rename within it; verified GET + PUT persist to the
host file. Seed with: cp tsheadroom.config.example.json config/config.json

Signed-off-by: Veslydev <vesly@vesly.dev>

@lkosewsk lkosewsk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it, thank you! I'm going to ask for one thing - can you take the PR description, condense it, and put it in the README (above or below the Run it as a service section - take your pick)? That gives new users the ability to get started fast without trying to figure out how to set up docker.

Condensed the deployment notes into a 'Run it with Docker' section under
Run, so new users can get started with docker compose up -d --build without
reverse-engineering the compose file.

Signed-off-by: Veslydev <vesly@vesly.dev>
@Veslydev Veslydev requested a review from lkosewsk June 12, 2026 19:45

@lkosewsk lkosewsk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A+, thank you! Clear to merge.

@lkosewsk lkosewsk merged commit ff3e212 into tailscale:main Jun 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants