This guide covers deploying Claude Task Master using Docker, including the unified server that runs both the REST API and MCP server with shared authentication.
- Quick Start
- Installation
- Docker Image
- Volume Mounts
- Environment Variables
- Authentication
- Docker Compose
- Production Deployment
- Troubleshooting
Important
Containers never mount a human's ~/.claude. The agent authenticates with its own scoped,
rotatable API key, held in an api-key profile. Set that up first —
the examples below assume a profile named agent in the claudetm-profiles volume.
The fastest way to get started with Claude Task Master in Docker:
Create the agent's credential once, into a named volume:
docker volume create claudetm-profiles
docker run --rm \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_API_KEY="$AGENT_API_KEY" \
ghcr.io/developerz-ai/claude-task-master:latest \
claudetm profile add agent --type api-key --base-url https://your-gateway.example/anthropicThen run the server against it:
# Pull the image from GitHub Container Registry
docker pull ghcr.io/developerz-ai/claude-task-master:latest
# Run with default settings (no auth, development only)
docker run -d \
--name claudetm \
-p 8000:8000 \
-p 8080:8080 \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_PROFILE=agent \
-v $(pwd):/app/project \
-v ~/.gitconfig:/home/claudetm/.gitconfig:ro \
-v ~/.config/gh:/home/claudetm/.config/gh:ro \
ghcr.io/developerz-ai/claude-task-master:latest
# Check logs
docker logs -f claudetm
# Access the services
# REST API: http://localhost:8000
# API Docs: http://localhost:8000/docs
# MCP Server: http://localhost:8080/sse# Production deployment with authentication
docker run -d \
--name claudetm \
-p 8000:8000 \
-p 8080:8080 \
-e CLAUDETM_PASSWORD=your-secure-password \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_PROFILE=agent \
-v $(pwd):/app/project \
-v ~/.gitconfig:/home/claudetm/.gitconfig:ro \
-v ~/.config/gh:/home/claudetm/.config/gh:ro \
ghcr.io/developerz-ai/claude-task-master:latestImages are automatically published to GitHub Container Registry on each release:
# Pull latest stable version
docker pull ghcr.io/developerz-ai/claude-task-master:latest
# Pull specific version
docker pull ghcr.io/developerz-ai/claude-task-master:1.0.0
# Pull specific version with architecture
docker pull --platform linux/amd64 ghcr.io/developerz-ai/claude-task-master:latestAvailable Platforms:
linux/amd64- x86_64 architecture (Intel/AMD)linux/arm64- ARM64 architecture (Apple Silicon, ARM servers)
Build the Docker image locally from the repository:
# Clone the repository
git clone https://github.com/developerz-ai/claude-task-master.git
cd claude-task-master
# Build the image
docker build -t claudetm .
# Build with specific version metadata
docker build \
--build-arg VERSION=1.0.0 \
--build-arg GIT_COMMIT=$(git rev-parse HEAD) \
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
-t claudetm .
# Build for specific platform
docker build --platform linux/amd64 -t claudetm .- Base Image:
python:3.12-slim - Size: ~300MB (multi-stage build for optimization)
- User: Non-root user
claudetm(UID 1000, GID 1000) - Working Directory:
/app/project - Entry Point:
claudetm-server(unified REST + MCP server)
The Dockerfile uses a multi-stage build for optimal image size and security:
- Builder Stage - Installs dependencies and packages
- Runtime Stage - Minimal production image with only required files
Images include OCI-compliant labels for version tracking:
# Inspect image metadata
docker inspect ghcr.io/developerz-ai/claude-task-master:latest | jq '.[0].Config.Labels'The image includes a built-in health check:
# Check container health
docker ps --filter name=claudetm --format "{{.Status}}"
# Manual health check
docker exec claudetm curl -f http://localhost:8000/healthClaude Task Master requires several volume mounts to function properly. Understanding these volumes is critical for proper deployment.
Important
The container must never mount or read a human's ~/.claude/.credentials.json. That file holds
a personal OAuth bearer token: a container given it runs as — and bills — that person's Claude
account, the token cannot be scoped to least privilege, and it cannot be rotated without breaking
that person's own Claude login. Anything that can read the container filesystem can read the token.
This has caused a real cross-developer credential leak; it is not a theoretical risk.
The agent gets its own scoped, rotatable credential instead, held in an api-key profile.
Purpose: the agent's own API credential (a scoped API key + base URL)
Mount: claudetm-profiles:/home/claudetm/.claudetm (a named volume, not a bind-mount of a home directory)
Read-only: ❌ No (claudetm writes the profile registry here)
How it works
claudetm's profiles (see "Profiles" in the README) already provide exactly
this. An api-key profile stores an API key and an Anthropic-compatible base URL, and claudetm
injects them as ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL at the SDK subprocess boundary. For an
api-key profile the OAuth credentials-file check is skipped entirely, so no ~/.claude is
required, expected, or read.
The registry lives at ~/.claudetm/profiles.json inside the container (relocatable with
CLAUDETM_HOME). Persist it in a named volume and select it per-run with CLAUDETM_PROFILE.
Setup:
-
Obtain a scoped agent credential. Issue an API key that belongs to the agent, not to a person — one key per consumer, revocable and rotatable on its own. Where that key comes from is outside this repository: it may be an Anthropic API key created for the agent, or a key minted by a central Claude proxy/gateway your organisation runs. Provisioning it is documented wherever that gateway lives, not here.
-
Create the profile in a named volume (one time, non-interactive —
profile addreads the key fromCLAUDETM_API_KEYinstead of prompting):docker volume create claudetm-profiles docker run --rm \ -v claudetm-profiles:/home/claudetm/.claudetm \ -e CLAUDETM_API_KEY="$AGENT_API_KEY" \ ghcr.io/developerz-ai/claude-task-master:latest \ claudetm profile add agent --type api-key \ --base-url https://your-gateway.example/anthropicOmit
--base-urlto talk toapi.anthropic.comdirectly. If the endpoint serves models under non-Anthropic ids, map them per tier with--model-opus/--model-sonnet/--model-haiku. -
Run the server against that profile:
docker run -d \ --name claudetm \ -p 8000:8000 \ -v claudetm-profiles:/home/claudetm/.claudetm \ -e CLAUDETM_PROFILE=agent \ -v $(pwd):/app/project \ ghcr.io/developerz-ai/claude-task-master:latest -
Verify:
docker exec claudetm claudetm profile list # 'agent' marked active, key masked docker exec claudetm claudetm doctor
Rotation: re-run step 2 with a new key (claudetm profile remove agent first, or add under a new
name and switch CLAUDETM_PROFILE). No human's login is touched, and nothing else in the deployment
changes.
Never put the key in the image, in docker-compose.yml, or in any file committed to version
control. Inject it at container-creation time from your secret store, and prefer a Docker/Swarm/K8s
secret over a plain -e where the platform supports one.
Troubleshooting:
claudetm profile listshows noagentprofile → the named volume is missing or empty; re-run step 2 with the same-v claudetm-profiles:....- "api-key profile 'agent' has an empty api_key" →
CLAUDETM_API_KEYwas unset when the profile was created; recreate it. - Auth errors from the endpoint → check
--base-urland that the key is still valid at the gateway.
Note
If you previously ran with -v ~/.claude:/home/claudetm/.claude:ro, remove that mount and treat the
token it exposed as compromised: rotate it by re-running /login in the Claude CLI on the host.
Purpose: Your repository/project that Claude Task Master will work on
Mount: $(pwd):/app/project or /path/to/your/project:/app/project
Read-only: ❌ No (server needs write access for commits, PRs, state)
The project directory is where Claude Task Master executes tasks, creates commits, and manages state.
What happens in this directory:
.claude-task-master/directory created for state- Git commits and branches created
- Code changes made during task execution
- Test runs and verification
Example Mounts:
# Current directory
-v $(pwd):/app/project
# Specific project path
-v /home/user/my-app:/app/project
# Windows (PowerShell)
-v ${PWD}:/app/project
# Windows (CMD)
-v %cd%:/app/projectPurpose: Git user configuration for commits
Mount: ~/.gitconfig:/home/claudetm/.gitconfig:ro
Read-only: ✅ Yes
Required for proper git commit attribution. Without this, commits may fail or use incorrect author information.
Minimal .gitconfig example:
[user]
name = Your Name
email = your.email@example.comPurpose: GitHub authentication for PR operations
Mount: ~/.config/gh:/home/claudetm/.config/gh:ro
Read-only: ✅ Yes
Required for creating and managing pull requests via the GitHub CLI.
Setup GitHub CLI:
# Install GitHub CLI
# See: https://cli.github.com/
# Authenticate
gh auth login
# Verify authentication
gh auth status
# Check config location
ls -la ~/.config/gh/Minimal Setup (Development):
docker run -d \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_PROFILE=agent \
-v $(pwd):/app/project \
ghcr.io/developerz-ai/claude-task-master:latestFull Setup (Production):
docker run -d \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_PROFILE=agent \
-v /path/to/project:/app/project \
-v ~/.gitconfig:/home/claudetm/.gitconfig:ro \
-v ~/.config/gh:/home/claudetm/.config/gh:ro \
-e CLAUDETM_PASSWORD=secure-password \
ghcr.io/developerz-ai/claude-task-master:latestThe container runs as user claudetm (UID 1000, GID 1000). Ensure mounted volumes have appropriate permissions:
# Check your UID
id -u # Should be 1000 for seamless mounting
# If not 1000, you may need to adjust permissions
# Option 1: Change ownership (if possible)
sudo chown -R 1000:1000 /path/to/project
# Option 2: Run container with your UID (not recommended for security)
docker run --user $(id -u):$(id -g) ...Configure Claude Task Master using environment variables. All variables are optional unless marked as required.
| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDETM_PASSWORD |
None | Password for REST API and MCP server authentication. Required for production. |
Example:
-e CLAUDETM_PASSWORD=your-secure-password| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDETM_SERVER_HOST |
No | 0.0.0.0 |
Host to bind to. Use 0.0.0.0 in containers. |
CLAUDETM_REST_PORT |
No | 8000 |
Port for REST API (FastAPI). |
CLAUDETM_MCP_PORT |
No | 8080 |
Port for MCP server. |
CLAUDETM_MCP_TRANSPORT |
No | sse |
MCP transport: sse or streamable-http. |
CLAUDETM_LOG_LEVEL |
No | info |
Log level: debug, info, warning, error. |
Example:
-e CLAUDETM_SERVER_HOST=0.0.0.0 \
-e CLAUDETM_REST_PORT=8000 \
-e CLAUDETM_MCP_PORT=8080 \
-e CLAUDETM_MCP_TRANSPORT=sse \
-e CLAUDETM_LOG_LEVEL=info| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDETM_CORS_ORIGINS |
No | None | Comma-separated list of allowed CORS origins. |
Example:
-e CLAUDETM_CORS_ORIGINS=http://localhost:3000,https://app.example.com| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDETM_WEBHOOK_URL |
No | None | URL to send webhook notifications. |
CLAUDETM_WEBHOOK_SECRET |
No | None | HMAC secret for webhook signature verification. |
Example:
-e CLAUDETM_WEBHOOK_URL=https://your-webhook.example.com/claudetm \
-e CLAUDETM_WEBHOOK_SECRET=webhook-secret-keySee Webhooks Documentation for more details on webhook events and payload formats.
| Variable | Required | Default | Description |
|---|---|---|---|
CLAUDETM_TARGET_BRANCH |
No | main |
Default target branch for pull requests. |
CLAUDETM_AUTO_MERGE |
No | true |
Auto-merge PRs once CI is green and review feedback is resolved. No approving review is required. |
CLAUDETM_MAX_SESSIONS |
No | None | Maximum number of task sessions. |
Example:
-e CLAUDETM_TARGET_BRANCH=develop \
-e CLAUDETM_AUTO_MERGE=false \
-e CLAUDETM_MAX_SESSIONS=10docker run -d \
--name claudetm \
-p 8000:8000 \
-p 8080:8080 \
\
# Authentication
-e CLAUDETM_PASSWORD=secure-password \
\
# Server
-e CLAUDETM_SERVER_HOST=0.0.0.0 \
-e CLAUDETM_REST_PORT=8000 \
-e CLAUDETM_MCP_PORT=8080 \
-e CLAUDETM_LOG_LEVEL=info \
\
# CORS
-e CLAUDETM_CORS_ORIGINS=http://localhost:3000 \
\
# Webhooks
-e CLAUDETM_WEBHOOK_URL=https://webhook.example.com/claudetm \
-e CLAUDETM_WEBHOOK_SECRET=webhook-secret \
\
# Tasks
-e CLAUDETM_TARGET_BRANCH=main \
-e CLAUDETM_AUTO_MERGE=true \
\
# Volumes
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_PROFILE=agent \
-v $(pwd):/app/project \
-v ~/.gitconfig:/home/claudetm/.gitconfig:ro \
-v ~/.config/gh:/home/claudetm/.config/gh:ro \
\
ghcr.io/developerz-ai/claude-task-master:latestClaude Task Master supports password-based authentication for both the REST API and MCP server using the same shared password.
Using Environment Variable (Recommended):
docker run -e CLAUDETM_PASSWORD=your-secure-password ...Using Docker Compose:
environment:
- CLAUDETM_PASSWORD=${CLAUDETM_PASSWORD}Using .env File with Docker Compose:
# .env file
CLAUDETM_PASSWORD=your-secure-password# Start with .env
docker compose upREST API:
- Uses
Authorization: Bearer <password>header - All endpoints except
/healthrequire authentication - Returns
401 Unauthorizedif missing or invalid
MCP Server:
- Same Bearer token authentication
- Applied to SSE and streamable-http transports
- Initial connection must include Authorization header
Without Authentication (401 Error):
curl http://localhost:8000/status
# Response: 401 UnauthorizedWith Authentication (Success):
curl -H "Authorization: Bearer your-secure-password" \
http://localhost:8000/statusTesting Webhook Configuration:
curl -X POST \
-H "Authorization: Bearer your-secure-password" \
-H "Content-Type: application/json" \
http://localhost:8000/webhooks/test-
Always use authentication in production
# ❌ Bad (no password) docker run -p 8000:8000 ghcr.io/developerz-ai/claude-task-master:latest # ✅ Good (with password) docker run -e CLAUDETM_PASSWORD=secure-password -p 8000:8000 ghcr.io/developerz-ai/claude-task-master:latest
-
Use strong passwords
- Minimum 16 characters
- Mix of letters, numbers, symbols
- Generate with password manager
-
Never hardcode passwords
# ❌ Bad docker run -e CLAUDETM_PASSWORD=password123 ... # ✅ Good (use environment variable) export CLAUDETM_PASSWORD=$(cat /path/to/secret) docker run -e CLAUDETM_PASSWORD ...
-
Use secrets management
- Docker Swarm secrets
- Kubernetes secrets
- HashiCorp Vault
- AWS Secrets Manager
-
Enable TLS/SSL in production
- Use reverse proxy (nginx, Caddy)
- Obtain SSL certificates (Let's Encrypt)
- Force HTTPS only
See Authentication Documentation for more details.
Docker Compose provides an easier way to manage multi-container deployments and configuration.
Create a docker-compose.yml file in your project:
services:
claudetm:
image: ghcr.io/developerz-ai/claude-task-master:latest
container_name: claudetm-server
restart: unless-stopped
ports:
- "8000:8000" # REST API
- "8080:8080" # MCP Server
volumes:
- claudetm-profiles:/home/claudetm/.claudetm
- .:/app/project
- ~/.gitconfig:/home/claudetm/.gitconfig:ro
- ~/.config/gh:/home/claudetm/.config/gh:ro
environment:
- CLAUDETM_PROFILE=agent
- CLAUDETM_PASSWORD=${CLAUDETM_PASSWORD}
- CLAUDETM_LOG_LEVEL=info
volumes:
claudetm-profiles:
external: trueThe repository includes a comprehensive docker-compose.yml with all options documented:
# Clone the repository
git clone https://github.com/developerz-ai/claude-task-master.git
cd claude-task-master
# Start with environment variables
CLAUDETM_PASSWORD=secure-password docker compose up
# Or use .env file
echo "CLAUDETM_PASSWORD=secure-password" > .env
docker compose up
# Run in background
docker compose up -d
# View logs
docker compose logs -f
# Stop and remove
docker compose down# Start services
docker compose up
# Start in background
docker compose up -d
# Rebuild and start
docker compose up --build
# View logs
docker compose logs -f claudetm
# Stop services
docker compose down
# Stop and remove volumes
docker compose down -v
# Restart service
docker compose restart claudetm
# Execute command in container
docker compose exec claudetm claudetm-py --versionCreate a .env file for Docker Compose:
# .env file
CLAUDETM_PASSWORD=your-secure-password
CLAUDETM_LOG_LEVEL=info
CLAUDETM_WEBHOOK_URL=https://webhook.example.com
CLAUDETM_WEBHOOK_SECRET=webhook-secret
# Custom paths
PROJECT_PATH=/path/to/your/project
# Agent credential: the api-key profile to run under (see Agent Credentials above).
# The key itself lives in the claudetm-profiles volume, never in this file.
CLAUDETM_PROFILE=agent# Start with .env
docker compose upThe repository includes comprehensive production-ready docker-compose examples for various deployment scenarios. All examples are located in the examples/docker-compose/ directory.
Available Examples:
- basic-production.yml - Simple production setup with essential features
- production-with-nginx.yml - Production with Nginx reverse proxy and SSL
- production-with-caddy.yml - Production with Caddy (automatic SSL)
- production-monitoring.yml - Production with Prometheus and Grafana
- development.yml - Development environment
See the Docker Compose Examples README for detailed documentation, usage instructions, and configuration guides for each example.
Quick Start with Production Examples:
# Navigate to examples directory
cd examples/docker-compose
# Copy the example you want to use
cp basic-production.yml docker-compose.yml
# Create .env file from template
cp .env.example .env
# Edit .env and set CLAUDETM_PASSWORD and other variables
# Start services
docker compose up -d
# View logs
docker compose logs -f
# Stop services
docker compose downBasic Production Example:
services:
claudetm:
image: ghcr.io/developerz-ai/claude-task-master:latest
container_name: claudetm-server
restart: always
ports:
- "8000:8000"
- "8080:8080"
volumes:
- claudetm-profiles:/home/claudetm/.claudetm
- ${PROJECT_PATH:-.}:/app/project
- ~/.gitconfig:/home/claudetm/.gitconfig:ro
- ~/.config/gh:/home/claudetm/.config/gh:ro
environment:
# Agent credential (required): api-key profile in the claudetm-profiles volume
- CLAUDETM_PROFILE=${CLAUDETM_PROFILE:-agent}
# API authentication (required)
- CLAUDETM_PASSWORD=${CLAUDETM_PASSWORD:?Password is required}
# Server config
- CLAUDETM_SERVER_HOST=0.0.0.0
- CLAUDETM_REST_PORT=8000
- CLAUDETM_MCP_PORT=8080
- CLAUDETM_MCP_TRANSPORT=${CLAUDETM_MCP_TRANSPORT:-sse}
- CLAUDETM_LOG_LEVEL=${CLAUDETM_LOG_LEVEL:-info}
# CORS
- CLAUDETM_CORS_ORIGINS=${CLAUDETM_CORS_ORIGINS:-}
# Webhooks
- CLAUDETM_WEBHOOK_URL=${CLAUDETM_WEBHOOK_URL:-}
- CLAUDETM_WEBHOOK_SECRET=${CLAUDETM_WEBHOOK_SECRET:-}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
# Created once out-of-band (see "Agent Credentials" above) so the agent's
# credential survives `docker compose down`. Set CLAUDETM_PROFILES_VOLUME to
# give a second deployment on the same host its own credential.
claudetm-profiles:
external: true
name: ${CLAUDETM_PROFILES_VOLUME:-claudetm-profiles}
networks:
default:
name: claudetm-networkFor more advanced production setups including SSL/TLS, monitoring, and high availability, see the examples directory.
Using Nginx:
# /etc/nginx/sites-available/claudetm
server {
listen 443 ssl http2;
server_name claudetm.example.com;
ssl_certificate /etc/letsencrypt/live/claudetm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/claudetm.example.com/privkey.pem;
# REST API
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# MCP Server SSE endpoint
location /sse {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
}
}Using Caddy:
# Caddyfile
claudetm.example.com {
reverse_proxy localhost:8000
reverse_proxy /sse localhost:8080 {
flush_interval -1
}
}# docker-stack.yml
version: "3.8"
services:
claudetm:
image: ghcr.io/developerz-ai/claude-task-master:latest
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
max_attempts: 3
ports:
- "8000:8000"
- "8080:8080"
volumes:
- type: volume
source: claudetm-profiles
target: /home/claudetm/.claudetm
- type: bind
source: /opt/projects
target: /app/project
environment:
- "CLAUDETM_PROFILE=${CLAUDETM_PROFILE:-agent}"
secrets:
- claudetm_password
# claudetm reads its API password from CLAUDETM_PASSWORD (or
# CLAUDETM_PASSWORD_HASH) only — there is no password-file option — so the
# mounted secret has to become that variable before the server starts.
# Without this shim the variable is empty, and an empty password means
# authentication is simply not installed on a server bound to 0.0.0.0.
entrypoint: ["/bin/sh", "-c"]
command:
- 'export CLAUDETM_PASSWORD="$$(cat /run/secrets/claudetm_password)"; exec claudetm-server'
volumes:
claudetm-profiles:
external: true
secrets:
claudetm_password:
external: trueDeploy:
# Create the API password secret
echo "your-secure-password" | docker secret create claudetm_password -
# Create the agent's scoped credential once, into the external volume
docker volume create claudetm-profiles
docker run --rm \
-v claudetm-profiles:/home/claudetm/.claudetm \
-e CLAUDETM_API_KEY="$AGENT_API_KEY" \
ghcr.io/developerz-ai/claude-task-master:latest \
claudetm profile add agent --type api-key \
--base-url https://your-gateway.example/anthropic
# Deploy stack
docker stack deploy -c docker-stack.yml claudetmNote
The stack mounts no human's ~/.claude. Every replica reads the same scoped api-key profile
from the claudetm-profiles volume, and rotating the key means recreating that profile — no
personal Claude login is involved. On a multi-node swarm the volume is per-node, so run the
profile add step once on each node that can schedule the service (or use a volume driver with
shared storage).
# claudetm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: claudetm
spec:
replicas: 2
selector:
matchLabels:
app: claudetm
template:
metadata:
labels:
app: claudetm
spec:
# The agent's scoped API key is written into a per-pod profile at startup.
# Nothing here mounts or reads a human's ~/.claude.
initContainers:
- name: create-profile
image: ghcr.io/developerz-ai/claude-task-master:latest
command:
- claudetm
- profile
- add
- agent
- --type
- api-key
- --base-url
- https://your-gateway.example/anthropic
env:
- name: CLAUDETM_API_KEY
valueFrom:
secretKeyRef:
name: claudetm-agent-credential
key: api-key
volumeMounts:
- name: agent-profile
mountPath: /home/claudetm/.claudetm
containers:
- name: claudetm
image: ghcr.io/developerz-ai/claude-task-master:latest
ports:
- containerPort: 8000
name: rest-api
- containerPort: 8080
name: mcp-server
env:
- name: CLAUDETM_PROFILE
value: "agent"
- name: CLAUDETM_PASSWORD
valueFrom:
secretKeyRef:
name: claudetm-secrets
key: password
- name: CLAUDETM_LOG_LEVEL
value: "info"
volumeMounts:
- name: agent-profile
mountPath: /home/claudetm/.claudetm
- name: project
mountPath: /app/project
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
volumes:
# Per-pod scratch: the init container recreates the profile on every start,
# so replicas never contend for one writable volume.
- name: agent-profile
emptyDir: {}
- name: project
persistentVolumeClaim:
claimName: project-pvc
---
apiVersion: v1
kind: Service
metadata:
name: claudetm
spec:
selector:
app: claudetm
ports:
- name: rest-api
port: 8000
targetPort: 8000
- name: mcp-server
port: 8080
targetPort: 8080Create the two Secrets (placeholders — never commit a real key):
# The agent's own scoped, rotatable API key
kubectl create secret generic claudetm-agent-credential \
--from-literal=api-key='REPLACE_WITH_SCOPED_AGENT_KEY'
# The REST/MCP API password
kubectl create secret generic claudetm-secrets \
--from-literal=password='REPLACE_WITH_API_PASSWORD'Why an init container and not just ANTHROPIC_API_KEY. Kubernetes is the one place where a
Secret is genuinely the right primitive — but it holds the agent's scoped API key, never a
human's OAuth credentials file. The key cannot simply be exported as ANTHROPIC_API_KEY: claudetm
runs a pre-flight credential check (get_valid_token) before starting a task, and that check is only
satisfied by an OAuth credentials file or an api-key profile. So the init container turns the
Secret into a profile in a shared emptyDir, and the app container selects it with
CLAUDETM_PROFILE=agent. CLAUDETM_PROFILE overrides the stored active-profile pointer per run, so
this works no matter what the init step left as active.
emptyDir is deliberate: the profile is cheap to recreate, it is recreated on every pod start, and
each replica gets its own copy — so scaling never has several pods writing one profile registry. Use
a PersistentVolumeClaim only if you want the profile to survive restarts, and then keep it
ReadWriteOnce with a single replica.
Rotation: update the claudetm-agent-credential Secret and restart the Deployment
(kubectl rollout restart deployment/claudetm). The init container rebuilds the profile from the new
key. No human's Claude login is touched.
Warning
Earlier versions of this guide mounted a human's ~/.claude (or a copied .claude directory, or a
claude-credentials Secret holding one) into these deployments. If you ran either of them that
way, remove the mount and treat the token it exposed as compromised — rotate it by re-running
/login in the Claude CLI on the host that owns it.
Prometheus Metrics (Future feature):
# prometheus.yml
scrape_configs:
- job_name: 'claudetm'
static_configs:
- targets: ['claudetm:8000']Centralized Logging:
# Using Docker logging driver
docker run \
--log-driver=syslog \
--log-opt syslog-address=udp://logserver:514 \
--log-opt tag="claudetm" \
ghcr.io/developerz-ai/claude-task-master:latestBackup Project State:
# Backup .claude-task-master directory
docker exec claudetm tar -czf - /app/project/.claude-task-master > backup-$(date +%Y%m%d).tar.gz
# Restore
docker exec -i claudetm tar -xzf - -C /app/project < backup-20240101.tar.gzCheck logs:
docker logs claudetmCommon causes:
- Missing agent credential: ensure the
claudetm-profilesvolume holds the api-key profile named byCLAUDETM_PROFILE(docker exec claudetm claudetm profile list) - Permission issues: Check volume mount permissions
- Port already in use: Change port mapping or stop conflicting services
Symptom: 401 Unauthorized responses
Solutions:
-
Verify password is set:
docker exec claudetm env | grep CLAUDETM_PASSWORD
-
Check Authorization header format:
curl -H "Authorization: Bearer your-password" http://localhost:8000/status -
Verify password matches:
# Check server logs for auth failures docker logs claudetm | grep -i auth
Two different failures, with two different messages — tell them apart before fixing anything.
Symptom A: Profile 'agent' not found. Run 'claudetm profile list' to see profiles.
CLAUDETM_PROFILE names a profile that is not in the registry. This is a hard failure:
ProfileManager.resolve_active raises ProfileNotFoundError rather than falling back to anything,
deliberately — silently running under the ambient credentials would mean billing the wrong account.
Usually the claudetm-profiles volume is empty (never created, or a fresh anonymous volume because
the named one was not declared external), or CLAUDETM_PROFILE is misspelled.
Symptom B: Credentials not found at /home/claudetm/.claude/.credentials.json
No profile is selected at all — CLAUDETM_PROFILE is unset and no active profile is stored — so
claudetm falls back to the ambient OAuth credentials file, which a container should not have. Set
CLAUDETM_PROFILE to the api-key profile; do not mount a human's ~/.claude to satisfy this.
Solutions:
-
See which profiles actually exist, and which one is active:
docker exec claudetm claudetm profile list -
Check the profile volume is mounted and
CLAUDETM_PROFILEnames one of those profiles:docker exec claudetm ls -la /home/claudetm/.claudetm/ docker exec claudetm env | grep CLAUDETM_PROFILE
-
Recreate the profile if the volume is empty:
docker run --rm \ -v claudetm-profiles:/home/claudetm/.claudetm \ -e CLAUDETM_API_KEY="$AGENT_API_KEY" \ ghcr.io/developerz-ai/claude-task-master:latest \ claudetm profile add agent --type api-key \ --base-url https://your-gateway.example/anthropic
Symptom: Git commits fail or PR creation fails
Solutions:
-
Mount git config:
-v ~/.gitconfig:/home/claudetm/.gitconfig:ro -
Mount GitHub CLI config:
-v ~/.config/gh:/home/claudetm/.config/gh:ro -
Verify GitHub authentication:
gh auth status
Symptom: Permission denied errors in logs
Solutions:
-
Check volume permissions:
ls -ld /path/to/project
-
Adjust ownership (if needed):
sudo chown -R 1000:1000 /path/to/project
-
Run with your UID (not recommended):
docker run --user $(id -u):$(id -g) ...
Solutions:
-
Limit container memory:
docker run --memory=2g --memory-swap=2g ...
-
Monitor resource usage:
docker stats claudetm
-
Adjust log levels:
-e CLAUDETM_LOG_LEVEL=warning
Enable debug logging:
docker run -e CLAUDETM_LOG_LEVEL=debug ...View detailed logs:
# Follow logs in real-time
docker logs -f claudetm
# Last 100 lines
docker logs --tail 100 claudetm
# Logs since 1 hour ago
docker logs --since 1h claudetmAccess container shell for debugging:
# Execute bash shell
docker exec -it claudetm bash
# Check environment
docker exec claudetm env
# Check processes
docker exec claudetm ps aux
# Check network
docker exec claudetm netstat -tlnpManually test health endpoint:
# From host
curl http://localhost:8000/health
# From container
docker exec claudetm curl http://localhost:8000/healthCheck health status:
docker inspect --format='{{.State.Health.Status}}' claudetm- Authentication Guide - Detailed authentication setup
- API Reference - REST API endpoint documentation
- Webhooks Guide - Webhook events and configuration
- Examples - Usage examples and tutorials
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Main README