Docker Guide
Deploy Archon on a server with Docker. Includes automatic HTTPS, PostgreSQL, and the Web UI.
Cloud-Init (Fastest Setup)
Section titled “Cloud-Init (Fastest Setup)”The fastest way to deploy. Paste the cloud-init config into your VPS provider’s User Data field when creating a server — it installs everything automatically.
File: deploy/cloud-init.yml
How to use
Section titled “How to use”- Create a VPS (Ubuntu 22.04+ recommended) at DigitalOcean, AWS, Linode, Hetzner, etc.
- Paste the contents of
deploy/cloud-init.ymlinto the “User Data” / “Cloud-Init” field - Add your SSH key via the provider’s UI
- Create the server and wait ~5-8 minutes for setup to complete
What it installs
Section titled “What it installs”- Docker + Docker Compose
- UFW firewall (ports 22, 80, 443)
- Clones the repo to
/opt/archon - Copies
.env.example->.envandCaddyfile.example->Caddyfile - Pre-pulls PostgreSQL and Caddy images
- Builds the Archon Docker image
After boot
Section titled “After boot”SSH into the server and finish configuration:
# Check setup completedcat /opt/archon/SETUP_COMPLETE
# Edit credentials and domainnano /opt/archon/.env
# Set at minimum:# CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...# DOMAIN=archon.example.com# DATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent
# (Optional) Set up basic auth to protect Web UI:# docker run caddy caddy hash-password --plaintext 'YOUR_PASSWORD'# Add to .env: CADDY_BASIC_AUTH=basicauth @protected { admin $$2a$$14$$<hash> }
# Startcd /opt/archondocker compose --profile with-db --profile cloud up -dDon’t forget DNS: Before starting, point your domain’s A record to the server’s IP.
Provider-specific notes
Section titled “Provider-specific notes”| Provider | Where to paste cloud-init |
|---|---|
| DigitalOcean | Create Droplet -> Advanced Options -> User Data |
| AWS EC2 | Launch Instance -> Advanced Details -> User Data |
| Linode | Create Linode -> Add Tags -> Metadata (User Data) |
| Hetzner | Create Server -> Cloud config -> User Data |
| Vultr | Deploy -> Additional Features -> Cloud-Init User-Data |
Local Docker Desktop (Windows / macOS)
Section titled “Local Docker Desktop (Windows / macOS)”Run Archon locally with Docker Desktop — no domain, no VPS required. Uses SQLite and the Web UI only.
Quick start
Section titled “Quick start”git clone https://github.com/coleam00/Archon.gitcd Archoncp .env.example .env# Edit .env: set CLAUDE_CODE_OAUTH_TOKEN or CLAUDE_API_KEYdocker compose up -dAccess the Web UI at http://localhost:3000.
Windows-specific notes
Section titled “Windows-specific notes”Build from WSL, not PowerShell. Docker Desktop on Windows cannot follow Bun workspace symlinks during the build context transfer. If you see The file cannot be accessed by the system, open a WSL terminal:
cd /mnt/c/Users/YourName/path/to/Archondocker compose up -dLine endings: The repo uses .gitattributes to force LF endings for shell scripts. If you cloned before this was added and see exec docker-entrypoint.sh: no such file or directory, re-clone or run:
git rm --cached -r .git reset --hardWhat you get
Section titled “What you get”| Feature | Status |
|---|---|
| Web UI | http://localhost:3000 |
| Database | SQLite (automatic, zero setup) |
| HTTPS / Caddy | Not needed locally |
| Auth | None (single-user, localhost only) |
| Platform adapters | Optional (Telegram, Slack, etc.) |
Using PostgreSQL locally (optional)
Section titled “Using PostgreSQL locally (optional)”docker compose --profile with-db up -dThen add to .env:
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agentManual Server Setup
Section titled “Manual Server Setup”Step-by-step alternative if you prefer not to use cloud-init, or need more control.
1. Install Docker
Section titled “1. Install Docker”# On Ubuntu/Debiancurl -fsSL https://get.docker.com | shsudo usermod -aG docker $USER
# Log out and back in for group change to take effectexit# ssh back in
# Verifydocker --versiondocker compose version2. Clone the repo
Section titled “2. Clone the repo”git clone https://github.com/coleam00/Archon.gitcd Archon3. Configure environment
Section titled “3. Configure environment”cp .env.example .envcp Caddyfile.example Caddyfilenano .envSet these values in .env:
# AI Assistant — at least one is required# Option A: Claude OAuth token (run `claude setup-token` on your local machine to get one)CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx# Option B: Claude API key (from console.anthropic.com/settings/keys)# CLAUDE_API_KEY=sk-ant-xxxxx
# Domain — your domain or subdomain pointing to this serverDOMAIN=archon.example.com
# Database — connect to the Docker PostgreSQL container# Without this, the app uses SQLite (fine for getting started, but PostgreSQL recommended)DATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent
# Basic Auth (optional) — protects Web UI when exposed to the internet# Skip if using IP-based firewall rules instead.# Generate hash: docker run caddy caddy hash-password --plaintext 'YOUR_PASSWORD'# CADDY_BASIC_AUTH=basicauth @protected { admin $$2a$$14$$... }
# Platform tokens (set the ones you use)# TELEGRAM_BOT_TOKEN=123456789:ABCdef...# SLACK_BOT_TOKEN=xoxb-...# SLACK_APP_TOKEN=xapp-...# GH_TOKEN=ghp_...# GITHUB_TOKEN=ghp_...Docker does not support
CLAUDE_USE_GLOBAL_AUTH=true— there is no localclaudeCLI inside the container. You must provide eitherCLAUDE_CODE_OAUTH_TOKENorCLAUDE_API_KEYexplicitly.If you use
--profile with-dbwithout settingDATABASE_URL, the app will fall back to SQLite and log a warning. The PostgreSQL container runs but is unused.
4. Point your domain to the server
Section titled “4. Point your domain to the server”Create a DNS A record at your domain registrar:
| Type | Name | Value |
|---|---|---|
| A | archon (or @ for root domain) | Your server’s public IP |
Wait for DNS propagation (usually 5-60 minutes). Verify with dig archon.example.com.
5. Open firewall ports
Section titled “5. Open firewall ports”sudo ufw allow 22/tcpsudo ufw allow 80/tcpsudo ufw allow 443sudo ufw --force enable6. Start
Section titled “6. Start”docker compose --profile with-db --profile cloud up -dThis starts three containers:
- app — Archon server + Web UI
- postgres — PostgreSQL 17 database (auto-initialized)
- caddy — Reverse proxy with automatic HTTPS (Let’s Encrypt)
7. Verify
Section titled “7. Verify”# Check all containers are runningdocker compose --profile with-db --profile cloud ps
# Watch logsdocker compose logs -f appdocker compose logs -f caddy
# Test HTTPS (from your local machine)curl https://archon.example.com/api/healthOpen https://archon.example.com in your browser — you should see the Archon Web UI.
Profiles
Section titled “Profiles”Archon uses Docker Compose profiles to optionally add PostgreSQL and/or HTTPS. Mix and match:
| Command | What runs |
|---|---|
docker compose up -d | App with SQLite |
docker compose --profile with-db up -d | App + PostgreSQL |
docker compose --profile cloud up -d | App + Caddy (HTTPS) |
docker compose --profile with-db --profile cloud up -d | App + PostgreSQL + Caddy |
No profile (SQLite)
Section titled “No profile (SQLite)”Zero-config default. No database container needed — SQLite file is stored in the archon_data volume.
--profile with-db (PostgreSQL)
Section titled “--profile with-db (PostgreSQL)”Starts a PostgreSQL 17 container. Set the connection URL in .env:
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agentThe schema is auto-initialized on first startup. PostgreSQL is exposed on ${POSTGRES_PORT:-5432} for external tools.
--profile cloud (Caddy HTTPS)
Section titled “--profile cloud (Caddy HTTPS)”Adds a Caddy reverse proxy with automatic TLS certificates from Let’s Encrypt.
Requires before starting:
Caddyfilecreated:cp Caddyfile.example CaddyfileDOMAINset in.env- DNS A record pointing to your server’s IP
- Ports 80 and 443 open
Caddy handles HTTPS certificates, HTTP->HTTPS redirect, HTTP/3, and SSE streaming.
Authentication (Optional Basic Auth)
Section titled “Authentication (Optional Basic Auth)”Caddy can enforce HTTP Basic Auth on all routes except webhooks (/webhooks/*) and the health check (/api/health). This is optional — skip it if you use IP-based firewall rules or other network-level access control.
To enable:
-
Generate a bcrypt password hash:
Terminal window docker run caddy caddy hash-password --plaintext 'YOUR_PASSWORD' -
Set
CADDY_BASIC_AUTHin.env(use$$to escape$in bcrypt hashes):CADDY_BASIC_AUTH=basicauth @protected { admin $$2a$$14$$abc123... } -
Restart:
docker compose --profile cloud restart caddy
Your browser will prompt for username/password when accessing the Archon URL. Webhook endpoints bypass auth since they use HMAC signature verification.
To disable, leave CADDY_BASIC_AUTH empty or unset — the Caddyfile expands it to nothing.
Important: Always use the
docker run caddy caddy hash-passwordcommand to generate hashes — never put plaintext passwords in.env.
Form-Based Authentication (HTML Login Page)
Section titled “Form-Based Authentication (HTML Login Page)”An alternative to basic auth that serves a styled HTML login form instead of the browser’s credential popup. Uses a lightweight auth-service sidecar and Caddy’s forward_auth directive.
When to use form auth vs basic auth:
- Form auth: Styled dark-mode login page, 24h session cookie, logout support. Requires an extra container.
- Basic auth: Zero extra containers, simpler setup. Browser shows a native credential dialog.
Setup:
-
Generate a bcrypt password hash:
Terminal window docker compose --profile auth run --rm auth-service \node -e "require('bcryptjs').hash('YOUR_PASSWORD', 12).then(h => console.log(h))"First run builds the auth-service image. Save the output hash (starts with
$2b$12$...). -
Generate a random cookie signing secret:
Terminal window docker run --rm node:22-alpine \node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -
Set the following in
.env:AUTH_USERNAME=adminAUTH_PASSWORD_HASH=$2b$12$REPLACE_WITH_YOUR_HASHCOOKIE_SECRET=REPLACE_WITH_64_HEX_CHARS -
Update
Caddyfile(copy fromCaddyfile.exampleif not done yet):- Uncomment the “Option A” form auth block (the
handle /login,handle /logout, andhandle { forward_auth ... }blocks) - Comment out the “No auth” default
handleblock (the lasthandle { ... }block near the bottom of the site block)
- Uncomment the “Option A” form auth block (the
-
Start with both
cloudandauthprofiles:Terminal window docker compose --profile with-db --profile cloud --profile auth up -d -
Visit your domain — you should be redirected to
/login.
Logout: Navigate to /logout to clear the session cookie and return to the login form.
Session duration: Defaults to 24 hours (COOKIE_MAX_AGE=86400). Override in .env:
COOKIE_MAX_AGE=3600 # 1 hourNote: Do not use form auth and basic auth simultaneously. Choose one method and leave the other disabled (either empty
CADDY_BASIC_AUTHor remove the basic auth@protectedblock from your Caddyfile).
Configuration
Section titled “Configuration”Port Defaults
Section titled “Port Defaults”The Docker healthcheck uses /api/health (not /health):
# Inside Dockercurl http://localhost:3000/api/health
# Local development (both work)curl http://localhost:3090/healthcurl http://localhost:3090/api/healthAI Credentials (required)
Section titled “AI Credentials (required)”Docker containers cannot use CLAUDE_USE_GLOBAL_AUTH=true — there is no local claude CLI inside the container. You must set credentials explicitly in .env:
Claude (choose one):
# OAuth token — run `claude setup-token` on your local machine, copy the tokenCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx
# Or API key — from console.anthropic.com/settings/keysCLAUDE_API_KEY=sk-ant-xxxxxCodex (alternative):
CODEX_ID_TOKEN=eyJhbGc...CODEX_ACCESS_TOKEN=eyJhbGc...CODEX_REFRESH_TOKEN=rt_...CODEX_ACCOUNT_ID=6a6a7ba6-...Platform Tokens (optional)
Section titled “Platform Tokens (optional)”TELEGRAM_BOT_TOKEN=123456789:ABCdef...SLACK_BOT_TOKEN=xoxb-...SLACK_APP_TOKEN=xapp-...DISCORD_BOT_TOKEN=...GH_TOKEN=ghp_...GITHUB_TOKEN=ghp_...WEBHOOK_SECRET=...Server Settings (optional)
Section titled “Server Settings (optional)”PORT=3000 # Default: 3000DOMAIN=archon.example.com # Required for --profile cloudLOG_LEVEL=info # fatal|error|warn|info|debug|traceMAX_CONCURRENT_CONVERSATIONS=10See .env.example for the full list with documentation.
Data Directory
Section titled “Data Directory”The container stores all data at /.archon/ (workspaces, worktrees, artifacts, logs, SQLite DB).
By default this is a Docker-managed volume. To store data at a specific location on the host, set ARCHON_DATA in .env:
# Store Archon data at a specific host pathARCHON_DATA=/opt/archon-dataThe directory is created automatically. Make sure the path is writable by UID 1001 (the container user):
mkdir -p /opt/archon-datasudo chown -R 1001:1001 /opt/archon-dataIf ARCHON_DATA is not set, Docker manages the volume automatically (archon_data) — data persists across restarts and rebuilds but lives inside Docker’s storage.
GitHub CLI Authentication
Section titled “GitHub CLI Authentication”GH_TOKEN from .env is picked up automatically. Alternatively:
docker compose exec app gh auth loginGitHub Webhooks
Section titled “GitHub Webhooks”After the server is reachable via HTTPS:
- Go to
https://github.com/<owner>/<repo>/settings/hooks - Add webhook:
- Payload URL:
https://archon.example.com/webhooks/github - Content type:
application/json - Secret: Your
WEBHOOK_SECRETfrom.env - Events: Issues, Issue comments, Pull requests
- Payload URL:
Pre-built Image
Section titled “Pre-built Image”For users who don’t need to build from source:
mkdir archon && cd archoncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/deploy/docker-compose.ymlcurl -O https://raw.githubusercontent.com/coleam00/Archon/main/.env.example
cp .env.example .env# Edit .env — set AI credentials, DOMAIN, etc.
docker compose up -dUses ghcr.io/coleam00/archon:latest. To add PostgreSQL, uncomment the postgres service in the compose file and set DATABASE_URL in .env.
To layer custom tools on top of the pre-built image, see Customizing the Image.
Building the Image
Section titled “Building the Image”The Dockerfile uses three stages:
- deps — Installs all dependencies (including devDependencies for the web build)
- web-build — Builds the React web UI with Vite
- production — Production image with only production dependencies + pre-built web assets
docker build -t archon .docker run --env-file .env -p 3000:3000 archonWhat’s in the image:
- Runtime: Bun 1.2 (runs TypeScript directly, no compile step)
- System deps: git, curl, gh (GitHub CLI), postgresql-client, Chromium
- Browser tooling: agent-browser (Vercel Labs) — enables E2E testing workflows via CDP. Uses system Chromium (
AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium) - App: All 10 workspace packages (source), pre-built web UI
- User: Non-root
appuser(UID 1001) — required by Claude Code SDK - Archon dirs:
/.archon/workspaces,/.archon/worktrees
The multi-stage build keeps the image lean — no devDependencies, test files, docs, or .git/.
Customizing the Image
Section titled “Customizing the Image”To add extra tools without modifying the tracked Dockerfile:
- Copy the example:
- Local/dev:
cp Dockerfile.user.example Dockerfile.user - Server/deploy:
cp deploy/Dockerfile.user.example Dockerfile.user
- Local/dev:
- Edit
Dockerfile.user— uncomment and extend the examples as needed. - Copy the override file:
- Local/dev:
cp docker-compose.override.example.yml docker-compose.override.yml - Server/deploy:
cp deploy/docker-compose.override.example.yml docker-compose.override.yml
- Local/dev:
- Run
docker compose up -d— Compose merges the override automatically.
Dockerfile.user and docker-compose.override.yml are gitignored so your customizations stay local.
Maintenance
Section titled “Maintenance”View Logs
Section titled “View Logs”docker compose logs -f # All servicesdocker compose logs -f app # App onlydocker compose logs --tail=100 app # Last 100 linesUpdate
Section titled “Update”git pulldocker compose --profile with-db --profile cloud up -d --buildRestart
Section titled “Restart”docker compose restart # Alldocker compose restart app # App onlydocker compose down # Stop containers (data preserved)docker compose down -v # Stop + delete volumes (destructive!)Database Migrations (PostgreSQL)
Section titled “Database Migrations (PostgreSQL)”Migrations run automatically on first startup via 000_combined.sql. When upgrading to a newer version that adds database tables, you need to apply incremental migrations manually:
# Example: apply the env vars migration (required when upgrading to v0.3.x)docker compose exec postgres psql -U postgres -d remote_coding_agent -f /migrations/020_codebase_env_vars.sqlThe migrations/ directory is mounted read-only into the postgres container. Check for any new migration files after pulling updates.
Clean Up Docker Resources
Section titled “Clean Up Docker Resources”docker system prune -a # Remove unused images/containersdocker volume prune # Remove unused volumes (caution!)docker system df # Check disk usageTroubleshooting
Section titled “Troubleshooting”App won’t start: “no_ai_credentials”
Section titled “App won’t start: “no_ai_credentials””No AI assistant configured. Docker does not support CLAUDE_USE_GLOBAL_AUTH=true. Set one of these in .env:
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...(runclaude setup-tokenlocally to get one)CLAUDE_API_KEY=sk-ant-...(from console.anthropic.com)- Or Codex credentials (
CODEX_ID_TOKEN,CODEX_ACCESS_TOKEN, etc.)
Caddy fails to start: “not a directory”
Section titled “Caddy fails to start: “not a directory””error mounting "Caddyfile": not a directoryThe Caddyfile doesn’t exist — Docker created a directory in its place. Fix:
rm -rf Caddyfilecp Caddyfile.example Caddyfiledocker compose --profile cloud up -dCaddy not getting SSL certificate
Section titled “Caddy not getting SSL certificate”# Check DNS propagationdig archon.example.com# Should return your server IP
# Check Caddy logsdocker compose logs caddy
# Check firewallsudo ufw status# Ports 80 and 443 must be openCommon causes: DNS not propagated (wait 5-60min), firewall blocking 80/443, domain typo in .env.
Health check failing
Section titled “Health check failing”The Docker healthcheck uses /api/health (not /health):
curl http://localhost:3000/api/healthPostgreSQL connection refused
Section titled “PostgreSQL connection refused”When using --profile with-db, ensure:
DATABASE_URLusespostgresas hostname (Docker service name), notlocalhost:DATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent- The postgres container is healthy:
docker compose ps postgres - Migrations ran: check
docker compose logs postgresfor init script output
Permission errors in /.archon/
Section titled “Permission errors in /.archon/”The container runs as appuser (UID 1001). If using bind mounts instead of Docker volumes:
sudo chown -R 1001:1001 /path/to/archon-dataPort conflicts
Section titled “Port conflicts”Default Docker port is 3000 (local dev is 3090). Change in .env:
PORT=3001Container keeps restarting
Section titled “Container keeps restarting”docker compose psdocker compose logs --tail=50 appCommon causes: missing .env file, invalid credentials, database unreachable.