Skip to content

Commit 5e29c06

Browse files
authored
fix(docker): persist headroom workspace in compose (headroomlabs-ai#1839)
## Description Pin the top-level Docker Compose proxy service to Headroom's canonical writable workspace under the existing `headroom_workspace` named volume. Closes headroomlabs-ai#1835 The dashboard's durable savings/history data is loaded from `proxy_savings.json` via `HEADROOM_WORKSPACE_DIR`; logs, session stats, TOIN, config, and default workspace state are also derived from that root. The top-level compose file already mounted `/home/nonroot/.headroom`, but it relied on image/user home resolution instead of exporting the canonical workspace env. This makes the official compose contract explicit and matches the Docker-native compose/runtime path behavior. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Set `HOME=/home/nonroot` for the top-level compose proxy service. - Set `HEADROOM_WORKSPACE_DIR=/home/nonroot/.headroom` and `HEADROOM_CONFIG_DIR=/home/nonroot/.headroom/config` so dashboard savings/history, logs, config, memory state, session stats, and TOIN resolve into the persisted named volume. - Added a regression test that locks the top-level compose persistence wiring. ## Testing - [x] Unit tests pass (`pytest`) — focused local tests and full CI test matrix passed - [x] Linting passes (`ruff check .`) — local Ruff and CI lint passed - [x] Type checking passes (`mypy headroom`) — local mypy and CI lint passed - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ rtk pytest tests/test_docker_compose_persistence.py Pytest: 1 passed $ rtk pytest tests/test_docker_compose_persistence.py tests/test_paths.py Pytest: 76 passed $ rtk uvx ruff check tests/test_docker_compose_persistence.py All checks passed! $ rtk docker compose config services: headroom-proxy: environment: HEADROOM_CONFIG_DIR: /home/nonroot/.headroom/config HEADROOM_HOST: 0.0.0.0 HEADROOM_WORKSPACE_DIR: /home/nonroot/.headroom HOME: /home/nonroot volumes: - type: volume source: headroom_workspace target: /home/nonroot/.headroom ``` Attempted broader proxy stats-history coverage, but this local checkout does not have the native extension built: ```text $ rtk pytest tests/test_docker_compose_persistence.py tests/test_paths.py tests/test_proxy_savings_history.py::test_stats_history_persists_across_restarts_and_stats_stays_compatible ModuleNotFoundError: No module named 'headroom._core' ``` Attempted project-managed Ruff, but `uv run` tried to build the editable package first and hit the known local native build issue before Ruff could execute: ```text $ rtk uv run ruff check tests/test_docker_compose_persistence.py error: failed to run custom build command for `esaxx-rs v0.1.10` fatal error: 'cstdint' file not found ``` ## Real Behavior Proof - Environment: local clean clone at current upstream `main`, branch `fix/1835-docker-compose-persistence`. - Exact command / steps: `rtk docker compose config` from the repo root. - Observed result: Compose renders `HOME`, `HEADROOM_WORKSPACE_DIR`, and `HEADROOM_CONFIG_DIR` under `/home/nonroot/.headroom`, and the `headroom_workspace` named volume targets that same path. - Not tested: full Docker image build or live `docker compose up` restart cycle; full pytest/mypy not run locally because this checkout lacks the built `headroom._core` extension. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A ## Additional Notes - All non-skipped GitHub Actions checks are green after the rebase onto `main`; skipped jobs are path-gated. - The dashboard's recent request table is still an in-memory tail and is expected to be empty after a proxy restart. This PR targets durable dashboard savings/history and other workspace-backed files. - `HEADROOM_LOG_FILE=/home/nonroot/.headroom/requests.jsonl` remains an optional operator setting; persisted request JSONL is not replayed into the dashboard after restart. - The docs/CHANGELOG checklist items are N/A for this narrow compose configuration fix.
1 parent e22d745 commit 5e29c06

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

‎docker-compose.yml‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ services:
44
command: ["--host", "0.0.0.0"]
55
environment:
66
- HEADROOM_HOST=0.0.0.0
7+
- HOME=/home/nonroot
8+
# Keep all Headroom read/write state on the named volume below.
9+
- HEADROOM_WORKSPACE_DIR=/home/nonroot/.headroom
10+
- HEADROOM_CONFIG_DIR=/home/nonroot/.headroom/config
711
# if you want to use a custom OpenAI-compatible API endpoint,
812
# uncomment and set the following line with the desired URL
913
# - OPENAI_TARGET_API_URL=https://api.x.ai
@@ -48,6 +52,6 @@ services:
4852
- NEO4J_apoc_import_file_use__neo4j__config=true
4953

5054
volumes:
51-
headroom_workspace: # persists proxy_savings, memory.db, session_stats, toin across image updates
55+
headroom_workspace: # persists dashboard savings/history, logs, config, memory state, session stats, and TOIN
5256
qdrant_data:
53-
neo4j_data:
57+
neo4j_data:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Regression checks for Docker Compose persistence wiring."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
ROOT = Path(__file__).resolve().parents[1]
8+
9+
10+
def test_top_level_compose_pins_headroom_state_to_named_volume() -> None:
11+
compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8")
12+
13+
assert "- headroom_workspace:/home/nonroot/.headroom" in compose
14+
assert "- HOME=/home/nonroot" in compose
15+
assert "- HEADROOM_WORKSPACE_DIR=/home/nonroot/.headroom" in compose
16+
assert "- HEADROOM_CONFIG_DIR=/home/nonroot/.headroom/config" in compose

0 commit comments

Comments
 (0)