Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/
.git/
tests/
docs/
.env
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Tailscale auth key — only needed on first run once state is persisted to the volume.
# Generate one at: https://login.tailscale.com/admin/settings/keys
TS_AUTHKEY=tskey-auth-xxxx

# Device name on the tailnet. Aperture reaches tsheadroom at http://<TS_HOSTNAME>.<tailnet>.ts.net/
TS_HOSTNAME=tsheadroom

# headroom-ai variant to install at build time.
# base — tool-output compression only (SmartCrusher); low memory, no ML model.
# ml — tool + text/prose compression (Kompress ML); ~600 MB/worker resident.
# Changing this requires a rebuild: docker compose up -d --build
HEADROOM_VARIANT=ml

# Number of persistent Python workers. Each holds a resident ML model copy when using ml.
# ~600 MB/worker — e.g. POOL_SIZE=4 uses ~2.4 GB. Size deliberately.
# Tip: start with POOL_SIZE=1 on a fresh volume to let the model download, then raise it.
POOL_SIZE=4
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ __pycache__/
/tsheadroom.config.json
*.config.json.tmp

# Local Docker config dir (bind-mounted into the container); copy of the example.
/config/

# tsnet state — contains tailscaled.state (the node's private key). Never commit.
# `t/` is the conventional local -state-dir; also guard the state files by name.
/t/
Expand Down
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Stage 1: build the Go binary
FROM golang:1.26 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /build/tsheadroom .

# Stage 2: Python runtime with headroom-ai
FROM python:3.13-slim
ARG HEADROOM_VARIANT=base

RUN python -m venv /venv

# Install the chosen headroom-ai variant.
# python:3.13-slim ships prebuilt wheels so no Rust toolchain is needed.
# 'ml' adds Kompress (~600 MB ML model downloaded on first use, cached in a volume).
RUN if [ "$HEADROOM_VARIANT" = "ml" ]; then \
/venv/bin/pip install --no-cache-dir 'headroom-ai[ml]'; \
else \
/venv/bin/pip install --no-cache-dir 'headroom-ai'; \
fi

COPY --from=builder /build/tsheadroom /app/tsheadroom
COPY worker.py /app/worker.py

ENTRYPOINT ["/app/tsheadroom"]
37 changes: 37 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
services:
tsheadroom:
build:
context: .
args:
HEADROOM_VARIANT: ${HEADROOM_VARIANT:-base}
command:
- -python
- /venv/bin/python
- -worker
- /app/worker.py
- -hostname
- ${TS_HOSTNAME:-tsheadroom}
- -pool-size
- "${POOL_SIZE:-4}"
- -state-dir
- /var/lib/tsheadroom
- -config
- /etc/tsheadroom/config.json
- -v
environment:
TS_AUTHKEY: ${TS_AUTHKEY:-}
volumes:
# Tailnet device identity. Must persist or the device re-authenticates as a new node.
- tsheadroom-state:/var/lib/tsheadroom
# HuggingFace ML model cache (~600 MB). Shared by all workers; survives restarts.
- tsheadroom-hf-cache:/root/.cache/huggingface
# Compression knobs. A directory (not a single file) so the binary's atomic
# PUT /config write can rename within it; edit ./config/config.json on the
# host and restart, or use the PUT /config API. Seed it once:
# mkdir -p config && cp tsheadroom.config.example.json config/config.json
- ./config:/etc/tsheadroom
restart: unless-stopped

volumes:
tsheadroom-state:
tsheadroom-hf-cache:
9 changes: 9 additions & 0 deletions tsheadroom.config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compress_user_messages": false,
"compress_system_messages": true,
"protect_recent": 4,
"protect_analysis_context": true,
"target_ratio": null,
"min_tokens_to_compress": 250,
"kompress_model": null
}