Docker container packaging Freshell with all supported coding CLI providers and common development tools. Designed as a persistent, browser-accessible, multi-device development environment.
Available in two variants: full (all providers baked in, ~4GB) and lite (providers installed on first boot, ~2GB).
Based on node:22-bookworm-slim (Debian). Alpine was evaluated but node-pty
(freshell's terminal spawning library) segfaults on musl libc.
Terminal multiplexer:
- Freshell — browser-based tabs, panes, session persistence, mobile-responsive UI
Code providers:
- Claude Code (Anthropic) — npm (
@anthropic-ai/claude-code)* - Codex CLI (OpenAI) — npm
- OpenCode (SST) — npm
- Antigravity CLI (Google) — official installer (
agycommand) - Kimi CLI (Moonshot AI) — uv (Python 3.13)
* Anthropic recommends the native installer over npm for Claude Code. However, the
native installer writes to ~/.local/bin which conflicts with the persistent
/home/coder volume mount, and its auto-updater creates additional conflicts at
runtime. The npm package installs to /usr/local/bin and is updated via container
image rebuilds, making it the better fit for Docker environments.
Note: Freshell's provider is still named "gemini" upstream. The GEMINI_CMD=agy
env var redirects it to launch Antigravity CLI.
Shells: bash (default), zsh, fish, dash — configurable via FRESHELL_SHELL env var
Dev tools:
- Version control: git, gh (GitHub CLI), ssh
- Editors / pagers: vim-tiny, nano, less
- Search / text: ripgrep, jq, yq, file, tree
- Transfer: curl, wget, rsync
- Networking diagnostics: ping, dig, traceroute, nc
- System diagnostics: lsof, htop, tmux
- Archives: tar, gzip, unzip
- Databases: sqlite3, psql (postgresql-client), mysql (mariadb-client)
- Containers: docker (CLI only — requires socket mount, see below)
- Agent skills: skills CLI — install and manage reusable skills for coding agents
- Misc: gnupg, bash-completion
docker run -d \
--name freshell \
-p 3001:3001 \
-e AUTH_TOKEN=$(openssl rand -hex 32) \
-v freshell-home:/home/coder \
ghcr.io/nkcx/freshell-container:latestdocker run -d \
--name freshell \
-p 3001:3001 \
-e AUTH_TOKEN=$(openssl rand -hex 32) \
-e PROVIDERS=claude,codex \
-v freshell-home:/home/coder \
-v freshell-providers:/opt/providers \
ghcr.io/nkcx/freshell-container:liteOpen http://localhost:3001 and enter your auth token.
The lite image ships the same base tools as full but without any pre-installed
coding CLI providers. Instead, providers are installed at runtime into a separate
/opt/providers volume based on the PROVIDERS environment variable.
Advantages:
- ~2GB smaller image (faster pulls, less storage)
- Install only the providers you need
- Provider updates without rebuilding the container
Trade-offs:
- First boot is slower (provider downloads)
- Requires network access on first boot
- Providers live on a separate volume that must be mounted
The PROVIDERS env var is a comma-separated list of provider names to install:
claude, codex, opencode, agy, kimi.
The MANAGE_PROVIDERS env var controls what happens at boot (default: install,uninstall,update):
| Mode | Behavior |
|---|---|
install |
Install providers in PROVIDERS that aren't already present |
uninstall |
Remove providers NOT in PROVIDERS that are present |
update |
Update all listed providers to their latest versions |
Examples:
MANAGE_PROVIDERS=install— only add new providers, never remove or updateMANAGE_PROVIDERS=install,uninstall— reconcile to matchPROVIDERSexactly, no updatesMANAGE_PROVIDERS=install,uninstall,update— (default) full reconcile plus update on every boot
PROVIDERS semantics:
- Not set — provider management is skipped entirely
- Empty (
PROVIDERS="") — withuninstallmode, removes all installed providers - Non-empty — reconcile against the list
Set UPDATE_CRON to a cron expression to automatically update providers on a schedule:
-e UPDATE_CRON="0 4 * * *" # update daily at 4amThis runs manage-providers.sh --modes update on the specified schedule using
supercronic.
The lite variant installs providers to /opt/providers, which must be mounted as
a separate Docker volume (-v freshell-providers:/opt/providers). Without this
mount, providers install into the container's writable layer and are lost on
container recreation. This volume separates provider binaries from user data
(/home/coder), allowing you to wipe it for a clean reinstall without losing
credentials, SSH keys, or project files.
See docker-compose.yaml for production-ready compose files
for both full and lite variants.
| Variable | Required | Default | Description |
|---|---|---|---|
AUTH_TOKEN |
Yes | (auto-generated) | Freshell authentication token (min 16 chars) |
PORT |
No | 3001 |
Freshell listen port |
TZ |
No | UTC | Container timezone |
FRESHELL_SHELL |
No | /bin/bash |
Shell for terminal sessions (/bin/zsh, /bin/fish, /bin/dash) |
ALLOWED_ORIGINS |
No | (auto-detect LAN) | Comma-separated CORS origins |
SKIP_UPDATE_CHECK |
No | true |
Disable freshell git-based auto-update |
PROVIDERS |
Lite only | — | Comma-separated providers to install: claude, codex, opencode, agy, kimi |
MANAGE_PROVIDERS |
Lite only | install,uninstall,update |
Provider management modes (see above) |
UPDATE_CRON |
Lite only | — | Cron expression for auto-updating providers (e.g., 0 4 * * *) |
SKILLS |
No | — | Comma-separated skill sources to install on boot (e.g., vercel-labs/agent-skills) |
EXTRA_PACKAGES |
No | — | Comma- or space-separated apt packages to install on boot (e.g., ffmpeg,imagemagick) |
CLAUDE_CMD |
No | claude |
Claude Code binary override |
CODEX_CMD |
No | codex |
Codex CLI binary override |
OPENCODE_CMD |
No | opencode |
OpenCode binary override |
GEMINI_CMD |
No | agy |
Freshell "gemini" provider binary (points to Antigravity CLI) |
KIMI_CMD |
No | kimi |
Kimi CLI binary override |
ANTHROPIC_API_KEY |
No | — | Anthropic API key (or authenticate interactively) |
OPENAI_API_KEY |
No | — | OpenAI API key (or authenticate interactively) |
GOOGLE_GENERATIVE_AI_API_KEY |
No | — | Gemini API key for Freshell AI tab summaries |
The /home/coder volume persists across container recreations:
~/.claude/— Claude Code credentials and session history~/.codex/— Codex CLI state~/.freshell/— Freshell configuration, state, and extensions~/.ssh/— SSH keys for git operations~/.gitconfig— Git configuration~/projects/— Cloned repositories (convention)
The /opt/providers volume (lite variant only) stores installed provider binaries
and their dependencies.
Freshell supports extensions in ~/.freshell/extensions/. This directory is
automatically created on first run. Since it lives inside the persistent home
volume, extensions installed at runtime survive container updates.
To inject extensions from an external volume at startup, mount a read-only volume at
/extensions. The entrypoint copies any files found there into ~/.freshell/extensions/
(without overwriting existing files).
The image ships with a general-purpose dev toolchain, but some workloads need
more — headless Chrome libraries for Playwright, media tools, database clients.
Set EXTRA_PACKAGES to a comma- or space-separated list of Debian packages and
the entrypoint installs them on boot:
environment:
EXTRA_PACKAGES: ffmpeg,imagemagick,postgresql-clientPackages come from the Debian bookworm repositories and are installed with
--no-install-recommends.
These packages are not persistent. They land in the container filesystem,
not in the /home/coder volume, so they are lost whenever the container is
recreated (an image update, a docker compose down). The entrypoint reinstalls
them on every boot, which adds startup time proportional to the list — already
present packages are a fast no-op, but the apt-get update still runs. For a
large or slow-changing set of packages, bake them into a derived image instead:
FROM ghcr.io/nkcx/freshell-container:latest
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg imagemagick \
&& rm -rf /var/lib/apt/lists/*
USER coderInstallation failures are non-fatal — a bad package name logs a warning and Freshell starts anyway. Check the container logs if an expected package is missing.
The coder user has passwordless sudo for exactly two commands — apt-get
and the entrypoint's apt cache cleanup helper — so this mechanism does not give
processes inside the container general root access. Note that sudo apt-get is
itself enough to install arbitrary Debian packages, which is a meaningful
privilege for anything running as coder, including AI agents.
The container ships with the skills CLI pre-installed, enabling reusable instruction sets for AI coding agents. Skills extend agent capabilities with specialized behaviors — release note generation, PR conventions, code review checklists, and more.
Set the SKILLS environment variable to a comma-separated list of skill sources:
-e SKILLS=vercel-labs/agent-skillsOn each boot, the entrypoint runs skills add <source> --yes --global for each
source, installing all discovered skills to every detected agent's global
directory (~/.claude/skills/, ~/.codex/skills/, etc.). Skills persist in the
/home/coder volume.
From any terminal inside the container:
# Browse and install skills from a repo
skills add vercel-labs/agent-skills
# Install a specific skill for a specific agent
skills add vercel-labs/agent-skills --skill frontend-design -a claude-code
# Use a skill without installing (piped to an agent)
skills use vercel-labs/agent-skills@web-design-guidelines | claude
# List installed skills
skills list
# Search the skills directory
skills find "code review"See skills.sh for the full skill directory.
Skills are agent instructions sourced from third-party repositories. A skill can direct an AI agent to run arbitrary commands, modify files, install packages, or exfiltrate data — with whatever permissions the agent has. Listing on a marketplace or directory does not imply a security review. Before installing:
- Read the skill's
SKILL.mdto understand what instructions it gives agents - Prefer skills from authors and organizations you trust
- Review installed skills with
skills listand inspect their contents - Be especially cautious with skills that ask agents to run shell commands, access credentials, or make network requests
This applies equally to skills installed manually and those auto-installed via
the SKILLS env var — the --yes flag skips interactive review, so vet your
sources before adding them to your compose file.
The container ships with the Docker CLI but no daemon. To use docker commands
from inside the container, mount the host's Docker socket:
volumes:
- /var/run/docker.sock:/var/run/docker.sockOr via docker run:
-v /var/run/docker.sock:/var/run/docker.sockYou'll also need to grant the coder user access to the socket. The simplest
approach is to add a supplementary group matching the host's docker group GID:
user: "1000:1000"
group_add:
- "${DOCKER_GID}" # host's `getent group docker | cut -d: -f3`Mounting the Docker socket gives this container root-equivalent access to the host. Anything running inside — including any AI coding agent you grant permissions to — can:
- Start privileged containers that bypass all isolation
- Mount any host filesystem path and read/write arbitrary files
- Access other containers' data, networks, and secrets
- Effectively become root on the host machine
Only enable this if you fully trust everything running inside the container, including the AI agents. In a single-user homelab this may be an acceptable trade-off; in a shared or production environment it almost certainly is not.
Alternatives to consider:
- Remote Docker context: configure
DOCKER_HOSTto point at a TCP socket with TLS client certificates — more access control, but more setup - Rootless Docker/Podman on the host: keeps socket access from escalating to host root, though containers can still interfere with each other
- Skip container support entirely: manage host containers from the host
- Open the Freshell UI in your browser
- Open a terminal tab
- Authenticate your preferred code provider (e.g.,
claudefor Claude Code) - Set up SSH key for your Git server:
ssh-keygen -t ed25519 -C "freshell" cat ~/.ssh/id_ed25519.pub # Add this public key to your Gitea/GitHub account
- Configure git identity:
git config --global user.name "Your Name" git config --global user.email "[email protected]"
- Clone a project and start working:
cd ~/projects git clone git@your-git-server:user/repo.git cd repo claude # or codex, opencode, agy, kimi
# Full variant (default) — all providers baked in
docker build --target full -t freshell-container .
# Lite variant — no providers, managed at runtime
docker build --target lite -t freshell-container:lite .
# Pin to a specific freshell version
docker build --target full --build-arg FRESHELL_VERSION=v0.7.0 -t freshell-container .| Tag | Freshell version | Description |
|---|---|---|
latest |
Latest stable release | Full image; production-ready; updated daily |
rc |
Latest release candidate | Full image; pre-release; updated daily when an RC exists |
lite |
Latest stable release | Lite image; providers installed at runtime |
lite-rc |
Latest release candidate | Lite pre-release |
v0.7.5 |
Pinned to Freshell v0.7.5 | Full image tracking a specific upstream release |
lite-v0.7.5 |
Pinned to Freshell v0.7.5 | Lite image tracking a specific upstream release |
sha-abc1234 |
Pinned to commit | Full image from a specific container commit |
When no release candidate exists (or the latest stable is newer than the latest RC),
rc points at the same image as latest, and lite-rc points at the same image
as lite.
The GitHub Actions workflow rebuilds daily to pick up new freshell releases
and base image security patches. Both latest (stable) and rc (release
candidate) tags are rebuilt on every run, along with their lite counterparts.
To trigger manually, use the workflow dispatch button on GitHub.
Freshell versions are resolved automatically from the upstream GitHub Releases
API — stable releases go to latest/lite, prereleases go to rc/lite-rc.
The full image is larger than typical containers (~4GB) due to bundling five code providers, each with their own dependency trees. The main contributors:
- npm global packages (Codex, OpenCode, Claude Code): ~1GB
- Antigravity CLI (Go binary): ~50MB
- Freshell + node_modules: ~600MB
- Kimi CLI + Python 3.13 environment: ~200MB
- build-essential (required for node-pty): ~200MB
The lite image is ~2GB — the same base without pre-installed providers. Providers are downloaded on first boot and stored on the provider volume.
build-essential is retained in the runtime image because freshell's npm run serve
triggers a build step that may recompile native modules. If freshell ships prebuilt
binaries or skips recompilation in a future release, this can be removed.
Freshell v0.7.0+ uses per-device tab tracking. Each browser gets a unique device ID (stored in localStorage), and open terminal tabs are scoped to that device. This is by design — a tab represents a live terminal session in a specific browser window.
Coding CLI sessions (Claude, Codex, etc.) are shared across all devices and appear in every browser's sidebar. Only the "open tabs" state is per-device.
If you want to see tabs from other devices, the sidebar shows a "remote" section with tabs open on other connected browsers.
- Claude Code auto-update is disabled by using the npm package instead of the
native installer. Updates come via container image rebuilds (full) or
UPDATE_CRON/ manualmanage-providers.sh --modes update(lite).
MIT. Individual code providers are subject to their respective terms of service.