Standalone batteries-included Docker image for Claude Code Studio.
This repository is intentionally independent from the CCS source repository. The Docker build fetches the selected CCS git ref at build time, installs Claude Code and Codex, adds tmux, configures tokless for Claude + Codex, and installs OpenMemory.
The default CI target is:
ghcr.io/<owner>/claude-code-studio:full
ghcr.io/<owner>/claude-code-studio:latest-full
The workflow also supports immutable release tags such as:
ghcr.io/<owner>/claude-code-studio:full-v7.18.3
The package is created from this repository's GHCR publishing workflow; it does not depend on an existing ghcr.io/lexus2016/claude-code-studio:latest base image.
docker run -d \
--name claude-code-studio \
-p 3000:3000 \
-v ccs-data:/app/data \
-v ccs-workspace:/app/workspace \
-v ccs-skills:/app/skills \
-v ccs-claude:/home/bun/.claude \
-v ccs-codex:/home/bun/.codex \
-v ccs-config:/home/bun/.config \
-v ccs-openmemory:/home/bun/.openmemory \
-v ccs-openmemory-store:/home/bun/.local/share/openmemory \
-v ccs-projectmem:/home/bun/.projectmem \
-v ccs-headroom:/home/bun/.headroom \
ghcr.io/malys/claude-code-studio:fullThe image runs as the bun user. Credentials are not baked into the image; authenticate the CLIs at runtime using the mechanisms documented by the respective providers.
- Claude Code CLI
- OpenAI Codex CLI
tmuxfor terminal/subscription engine supporttoklessconfigured for Claude Code and Codex- OpenMemory CLI for Claude Code/Codex session portability
- ProjectMem MCP for project-scoped persistent memory
- Headroom MCP for on-demand context compression and retrieval
- CCS itself, fetched from the upstream
Lexus2016/claude-code-studiorepository
At every container start the entrypoint resolves CCS_REF upstream and
reinstalls CCS when it points at a different commit than the running tree
(/app/.ccs-revision). Nothing else is touched — the CLIs, plugins and MCP
servers move with the image.
| Variable | Default | Effect |
|---|---|---|
CCS_AUTO_UPDATE |
1 |
0 disables the check entirely |
CCS_REPO |
upstream CCS repository | Source to track |
CCS_REF |
branch/tag baked at build time | A tag only moves when the tag does |
The new tree is cloned and installed in a temp directory and only then
swapped into /app, so a failed clone or install leaves the running version
alone. /app/data, /app/workspace and /app/skills are volumes and are never
part of the swap. The update lives in the container's writable layer: it
survives restart, and a docker compose pull + recreate replaces it with the
image's own version. Expect the start to take as long as a bun install when an
update actually lands.
scripts/ccs-fetch.sh builds that tree and is the same script the image build
uses, so a self-updated container and a rebuilt image run identical code.
sh scripts/test-ccs-fetch.sh checks it against a throwaway repository.
| Path | Purpose |
|---|---|
/app/data |
CCS application data and config |
/app/workspace |
User workspaces/projects |
/app/skills |
CCS skill directory |
/home/bun/.claude |
Claude state/auth/skills |
/home/bun/.codex |
Codex state/auth/config |
/home/bun/.config |
User config used by CLI tooling |
/home/bun/.openmemory |
OpenMemory state |
/home/bun/.local/share/openmemory |
OpenMemory ledger/runtime state |
/home/bun/.projectmem |
ProjectMem project registry |
/home/bun/.headroom |
Headroom MCP session stats (the compression store is in memory) |
ProjectMem and Headroom are added idempotently to the CCS, Codex and user-scope
Claude (~/.claude.json) MCP config at container startup, so chat runs and
terminal / docker exec sessions reach both servers. Existing entries with the
same names are preserved.
Every directory under WORKDIR is registered with ProjectMem (pjm init --no-watch) at startup, so each project has its own memory; a project added
while the container runs needs a restart to be picked up.
Every directory under WORKDIR is also indexed by CodeGraph (codegraph init -y) at startup when it has no .codegraph/ yet, so a project added after the
image was built is reachable by the codegraph_explore MCP tool instead of
silently falling back to grep. The sweep runs in the background — a first index
of a large repo takes minutes and must not delay the server — and it skips any
project that is already indexed. Set CCS_CODEGRAPH_INDEX=0 to disable it.
CCS_AGENT_RULES pins instructions every Claude and Codex session must get
(for example "no heavy builds or test suites on this host"). At each start it
is written at the top of ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md, between
<!-- ccs-agent-rules:begin --> / :end --> markers. A rule added by hand at the end of those
files does not last: the tokless wiring that runs at startup rewrites everything
below its own block. Unset the variable to remove the pinned block.
The tokless-installed package skills (context-mode's ctx-search, ctx-index,
ctx-doctor, …) are re-linked into /home/bun/.claude/skills and mirrored into
/app/skills on every start. They are baked into the image, but both of those
paths are named volumes, and Docker seeds a named volume from the image only
while the volume is still EMPTY: a volume created by an earlier image keeps its
old content forever, so skills added by a newer image would never show up. The
symptom is "tokless is no longer installed" on a container recreated against an
updated image, even though tokless itself reports every tool green — the
binary and its MCP servers live in an image layer and are unaffected.
Use
docker exec -u bun, not a root shell. The container starts as root so the entrypoint can fix bind-mount ownership, sodocker exec -ti claude-code-studio bashlands you as root withHOME=/home/bun. Anything run there —tokless,codegraph,claude— writes root-owned files into thebunuser's home and into project directories; the server then runs asbunand cannot write them. A root-owned<project>/.codegraphreportsattempt to write a readonly databaseand the project looks un-indexed. The entrypoint'schown -Rrepairs it on the next container restart.
Headroom runs in MCP-only mode: no proxy, dashboard or extra network port,
and no Headroom memory (that feature belongs to headroom proxy). It offers
on-demand headroom_compress / headroom_retrieve / headroom_stats only;
nothing is compressed automatically, and a compressed hash is retrievable only
within the MCP process that produced it. This differs from tokless, whose
Headroom support (unreleased past v0.3.2) is proxy-only via
ANTHROPIC_BASE_URL=http://127.0.0.1:8787 and deliberately refuses MCP wiring.
.github/workflows/publish.yml publishes multi-architecture images (linux/amd64 and linux/arm64) to GHCR on:
- weekly schedule
- manual
workflow_dispatch repository_dispatchevent typeccs-release- git version tags pushed to this repository
A release automation in the CCS repository can notify this repository with a repository_dispatch payload such as:
{
"event_type": "ccs-release",
"client_payload": {
"ccs_ref": "v7.18.3",
"version": "7.18.3"
}
}For that cross-repository dispatch, the caller must have a token with permission to dispatch workflows in this repository. The image workflow itself uses the repository's GITHUB_TOKEN only for GHCR publishing.
docker build \
--build-arg CCS_REF=main \
--build-arg CLAUDE_CODE_VERSION=latest \
--build-arg CODEX_VERSION=latest \
--build-arg TOKLESS_REF=main \
--build-arg OPENMEMORY_REF=main \
--build-arg PROJECTMEM_VERSION=0.3.2 \
--build-arg HEADROOM_VERSION=0.37.0 \
-t ccs-full:local .For reproducible releases, replace the rolling refs with immutable upstream tags or commit SHAs.
Authentication and provider credentials are deliberately excluded from the image. Persistent agent directories are volumes so first-run authentication and session state survive container recreation. The Dockerfile runs the application as a non-root user.