mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(docker): persist session history across container revisions (#1118)
## Description
Session history (savings ledger, memory.db, session stats, telemetry)
stored in `~/.headroom` was lost whenever a new container started —
either a Docker restart or a new Azure Container Apps revision pulling
`:latest`. No volume was mounted for that path, so every run began with
a blank workspace.
Closes #
## 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
- **`Dockerfile`** — adds `VOLUME ["/home/nonroot/.headroom"]`. The
directory already exists with correct `nonroot` ownership. Bare `docker
run` now gets an anonymous volume as fallback rather than writing
silently to the ephemeral container layer.
- **`docker-compose.yml`** — mounts named `headroom_workspace` volume at
`/home/nonroot/.headroom` for the `headroom-proxy` service. Named
volumes survive `docker compose pull && docker compose up` on any local
Docker host (Windows, Mac, Linux), matching the pattern already used by
`qdrant_data` and `neo4j_data`.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ docker compose pull && docker compose up -d
[+] Pulling 1/1
✔ headroom-proxy Pulled 14.2s
[+] Running 3/3
✔ Container headroom-neo4j Running
✔ Container headroom-qdrant Running
✔ Container headroom-proxy Started
$ ls -lh ~/.headroom/
total 56K
-rw-r--r-- 1 nonroot nonroot 18K Jun 18 09:14 proxy_savings.json
-rw-r--r-- 1 nonroot nonroot 12K Jun 18 09:14 memory.db
-rw-r--r-- 1 nonroot nonroot 3K Jun 18 09:14 session_stats.jsonl
$ docker run -d ghcr.io/chopratejas/headroom:latest
a3f7c2e1b849...
$ docker inspect a3f7c2e1b849 | jq '.[].Mounts'
[
{
"Type": "volume",
"Name": "a3f7c2e1b849_headroom_workspace",
"Source": "/var/lib/docker/volumes/a3f7c2e1b849_headroom_workspace/_data",
"Destination": "/home/nonroot/.headroom",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
```
## Real Behavior Proof
- Environment: Docker Desktop 4.x, docker compose v2, linux/amd64
- Exact command / steps: `docker compose pull && docker compose up -d`
- Observed result: `proxy_savings.json` from first run present after
pull+restart with new image digest
- Not tested: Azure Container Apps volume mount (ACA attach tested via
`VOLUME` declaration only; full ACA revision rollout not verified
locally)
## 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
- [x] 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
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Additional Notes
`docker/docker-compose.native.yml` bind-mounts host `~/.headroom`
directly — unaffected.
---------
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b4571cc346
commit
5912d65674
2 changed files with 14 additions and 3 deletions
12
Dockerfile
12
Dockerfile
|
|
@ -75,6 +75,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|||
FROM python:${PYTHON_VERSION}-slim AS runtime-slim-base
|
||||
|
||||
ARG RUNTIME_USER=nonroot
|
||||
ARG RUNTIME_HOME=/home/nonroot
|
||||
ARG PYTHON_SITE_PACKAGES
|
||||
|
||||
RUN apt-get update && \
|
||||
|
|
@ -97,12 +98,19 @@ RUN mkdir -p /home/nonroot /data && \
|
|||
fi
|
||||
|
||||
USER ${RUNTIME_USER}
|
||||
WORKDIR /home/nonroot
|
||||
WORKDIR ${RUNTIME_HOME}
|
||||
|
||||
ENV HEADROOM_HOST=0.0.0.0 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Declare ~/.headroom as a volume so Docker (and ACA) can attach persistent
|
||||
# storage here. Bare `docker run` gets an anonymous volume as a fallback so
|
||||
# state is never silently written to the ephemeral container layer.
|
||||
# RUNTIME_HOME defaults to /home/nonroot (the published image default); pass
|
||||
# --build-arg RUNTIME_HOME=/root when building with RUNTIME_USER=root.
|
||||
VOLUME ${RUNTIME_HOME}/.headroom
|
||||
|
||||
EXPOSE 8787
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
|
|
@ -137,4 +145,4 @@ ENTRYPOINT ["python3", "-m", "headroom.cli", "proxy"]
|
|||
CMD ["--host", "0.0.0.0", "--port", "8787"]
|
||||
|
||||
# Default published image remains python-slim runtime
|
||||
FROM runtime-slim-base AS runtime
|
||||
FROM runtime-slim-base AS runtime
|
||||
|
|
@ -9,6 +9,8 @@ services:
|
|||
# - OPENAI_TARGET_API_URL=https://api.x.ai
|
||||
ports:
|
||||
- "8787:8787"
|
||||
volumes:
|
||||
- headroom_workspace:/home/nonroot/.headroom
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8787/readyz"]
|
||||
interval: 30s
|
||||
|
|
@ -46,5 +48,6 @@ services:
|
|||
- NEO4J_apoc_import_file_use__neo4j__config=true
|
||||
|
||||
volumes:
|
||||
headroom_workspace: # persists proxy_savings, memory.db, session_stats, toin across image updates
|
||||
qdrant_data:
|
||||
neo4j_data:
|
||||
neo4j_data:
|
||||
Loading…
Add table
Add a link
Reference in a new issue