Docker Setup
Deploy using Docker and Docker Compose
Files Overview
Dockerfile- Production-optimized multi-stage buildDockerfile.dev- Development build with hot reloadingdocker-compose.yml- Orchestration for production and development.dockerignore- Excludes unnecessary files from Docker context
Quick Start
Production Build
-
Build and run with Docker Compose:
docker compose up -d --build workflow-app
-
Or build and run manually:
Build the image:
docker build -t workflow-app .
Run the container:
docker run -p 3000:3000 --name workflow-app workflow-app
-
Access the application: Open http://localhost:3000 in your browser
Development Setup
You have two dev-friendly options:
-
Hot reload (recommended):
workflow-dev(detached)docker compose up -d --build workflow-dev
This runs
pnpm devinside the container with your repo bind-mounted for fast iteration. By default,workflow-devbinds tohttp://localhost:3000.If you see
Bind for 0.0.0.0:3000 failed: port is already allocated, it means something else is already using port 3000 (oftenworkflow-app). Stop the other service before startingworkflow-dev:docker compose stop workflow-app
docker compose up -d --build workflow-dev
-
Auto-rebuild on file changes (slower):
docker compose watchThe production-like service (
workflow-app) hasdevelop.watchconfigured indocker-compose.yml. This will rebuild/recreate the container when files change:docker compose watch workflow-app
Running Detached (Recommended)
If you want the container logs to show in Docker Desktop (not your terminal), run in detached mode:
Dev (hot reload)
docker compose up -d --build workflow-dev
Open: http://localhost:3000
App (prod-like)
docker compose up -d --build workflow-app
Open: http://localhost:3000
Viewing logs
-
Docker Desktop: open the container and view the Logs tab
-
CLI (optional):
docker compose logs -f workflow-dev
or:
docker compose logs -f workflow-app
Stopping
docker compose stop workflow-dev
docker compose stop workflow-app
Environment Variables
The application supports configuration through environment variables. You can:
- Use the Settings UI (recommended) - All configuration can be done through the web interface
- Set environment variables - Either in docker-compose.yml or via .env file
Common Environment Variables
GitLab Configuration:
GITLAB_URL=https://gitlab.com GITLAB_TOKEN=your_gitlab_token_here
Claude Configuration:
ANTHROPIC_API_KEY=your_claude_api_key_here # Optional override for the sandbox wrapper path used to run Claude Code non-interactively CLAUDE_CODE_WRAPPER=/usr/local/bin/claude-code-wrapper
Claude Code Authentication
This app runs Claude Code via the claude CLI inside the container.
- Platform account (API key login): If you have an Anthropic platform account, set
ANTHROPIC_API_KEY(recommended for headless/automation). - Claude subscription (manual login): If you only have a Claude subscription plan (not a platform account), you typically cannot use an API key and must log in interactively once.
If you are using a subscription/manual login, set:
CLAUDE_CODE_AUTH_MODE=cli
This forces the app to not pass ANTHROPIC_API_KEY into the claude subprocess even if it is set in the container environment.
One-time manual login inside Docker
-
Exec into the running container:
docker exec -it workflow-app bash
or (dev):
docker exec -it workflow-dev bash
-
Run an interactive Claude CLI session and follow the prompts (it will typically print a URL + code):
claude --help # Start interactive mode (this is the most version-stable way to complete auth + trust): claude
-
Exit and restart the app container:
exit
docker compose restart workflow-app
Persisting the login across rebuilds/restarts
Claude Code stores its auth + settings under ~/.claude inside the container.
docker-compose.yml mounts a named volume so your login persists:
/home/nextjs/.claude(the app runs as thenextjsuser)/root/.claude(if you exec into the container as root and runclaude, it may write here)
Important: Claude also writes a ~/.claude.json file in the user's home directory.
The container startup scripts migrate this file into ~/.claude/.claude.json and symlink it back,
so it persists in the same named volume across restarts.
If you want a single consistent location, log in as nextjs (recommended):
docker exec -it --user nextjs workflow-app bash
cd /app/workspaces
claude
Trusted directory (important)
When Claude prompts you to "trust" a directory, do it from the directory you want Claude to operate in.
For this app, that's typically /app/workspaces (or a specific repo under it).
Important nuance: this app typically runs Claude Code in non-interactive mode using -p/--print (and --output-format stream-json).
Per the Claude CLI help, the workspace trust dialog is skipped in -p mode, which is why "headless" commands can appear to work even if you haven't completed the interactive trust/setup flow yet.
If you want to "do it the right way" once and have it persist, run an interactive Claude session (no -p) from /app/workspaces and complete the trust prompt once:
Git Bash / mintty on Windows: use winpty for proper TTY
winpty docker exec -it --user nextjs workflow-dev bash
cd /app/workspaces
claude
Example (dev or prod):
docker exec -it --user nextjs workflow-dev bash
cd /app/workspaces
run any claude command once to trigger the trust prompt if needed
claude --help
To "log out" / reset Claude CLI credentials, remove the volume (this deletes the stored login):
docker compose down
docker volume rm workflow_workflow_claude_config
Advanced Configuration
MAX_WORKSPACE_SIZE_MB=500 TEMP_DIR_PREFIX=gitlab-claude- LOG_LEVEL=info ALLOWED_GITLAB_HOSTS=gitlab.com MAX_CONCURRENT_WORKSPACES=3
Data Persistence
The Docker setup includes a named volume workflow_workspaces to persist:
- Cloned repositories
- Workspace data
- Configuration files
Useful Docker Commands
Managing the Application
Start the application:
docker compose up -d
Stop the application:
docker compose down
View logs:
docker compose logs -f workflow-app
Restart the application:
docker compose restart workflow-app
Update and rebuild:
docker compose up --build -d
Development Commands
Access the container shell:
docker exec -it workflow-app bash
or (dev):
docker exec -it workflow-dev bash
Run tests inside container:
docker exec -it workflow-app bash -lc "pnpm test"
Check application health:
docker exec -it workflow-app bash -lc "wget -qO- http://localhost:3000/api/config/validate"
Cleanup
Remove containers and networks:
docker compose down
Remove containers, networks, and volumes:
docker compose down -v
Remove all related images:
docker rmi workflow-app workflow-app-dev
Clean up dangling images:
docker image prune
Production Deployment
Plain Docker Compose (single host)
If you're deploying to a single VM (no Swarm), and you have a reverse proxy (Traefik/Caddy/nginx) handling TLS:
docker compose up -d --build workflow-app
Notes:
- When deploying behind a reverse proxy,
workflow-appshould not publish3000to the host. The repodocker-compose.ymlis set up that way by default; your reverse proxy should publish80/443and route internally toworkflow-app:3000. - Set
NEXTAUTH_URLto your public URL (e.g.https://workflow.example.com) in production.
Docker Swarm
Initialize swarm (if not already done):
docker swarm init
Deploy stack:
docker stack deploy -c docker-compose.yml workflow
Notes:
- If you are deploying behind Traefik/Caddy,
workflow-appshould not publish3000to the host. Your reverse proxy should publish80/443and route internally toworkflow-app:3000. - Set
NEXTAUTH_URLto your public URL (e.g.https://workflow.example.com) in production.
Kubernetes
You can use the Docker images with Kubernetes. Example deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: workflow-app
spec:
replicas: 3
selector:
matchLabels:
app: workflow-app
template:
metadata:
labels:
app: workflow-app
spec:
containers:
- name: workflow-app
image: workflow-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: 'production'
Troubleshooting
Common Issues
-
Port already in use:
If you are running the app directly (no reverse proxy), change the port mapping in
docker-compose.yml:ports: - '3001:3000'
If you are running behind a reverse proxy (Traefik/Caddy/nginx), the recommended fix is to not publish the app port at all:
- Remove
ports:from the app service - Keep the app reachable only on an internal Docker network
- Have the reverse proxy publish
80/443and route toworkflow-app:3000
- Remove
-
Permission issues with workspaces:
Fix volume permissions:
docker compose exec workflow-app chown -R nextjs:nodejs /app/workspaces
-
Build failures:
Clean build with no cache:
docker compose build --no-cache
Local testing with ngrok
If you want to test webhooks (e.g. n8n callbacks) or access the app from outside your network:
-
Start the app locally (prod or dev):
docker compose up -d workflow-app
or:
docker compose up workflow-dev --build
-
Expose port 3000:
ngrok http 3000
-
Use the printed URL (e.g.
https://xxxx.ngrok-free.app) as your base URL:POST https://xxxx.ngrok-free.app/api/askPOST https://xxxx.ngrok-free.app/api/editGET https://xxxx.ngrok-free.app/api/jobs/:jobId
Notes:
- If you are testing NextAuth session login through ngrok, you must set
NEXTAUTH_URLto the ngrok URL. - If you are using API key auth,
NEXTAUTH_URLis not required.
-
Memory issues:
Increase Docker memory limit in Docker Desktop settings, or add memory limits to docker-compose.yml:
deploy: resources: limits: memory: 1G
Health Checks
The container includes health checks that verify:
- Application is responding on port 3000
- API endpoints are accessible
- Configuration validation passes
Check health status:
docker-compose ps
docker inspect --format='{{.State.Health.Status}}' workflow-appPerformance Optimization
Production Tips
- Multi-stage builds - Already implemented to minimize image size
- Layer caching - Dependencies are cached separately from source code
- Non-root user - Runs as
nextjsuser for security - Standalone output - Uses Next.js standalone mode for optimal performance
Monitoring
Add monitoring with tools like:
- Prometheus + Grafana
- Docker stats:
docker stats workflow-app - Health endpoint:
curl http://localhost:3000/api/config/validate
Security Considerations
-
Secrets management - Use Docker secrets or external secret management
-
Network security - Consider using custom networks
-
Image scanning - Regularly scan images for vulnerabilities
-
Updates - Keep base images and dependencies updated
-
Reverse proxy (Traefik/Caddy) recommended:
-
Terminate TLS at the reverse proxy and forward traffic to the app over a private network.
-
Do not publish the app container port to the internet; only the reverse proxy should connect to it.
-
If you enable API IP allowlisting (
ALLOWED_IPS), configure the proxy to overwrite/sanitizeX-Forwarded-For/X-Real-IPso clients cannot spoof their IP. -
Caddy example:
reverse_proxy workflow-app:3000 { header_up X-Forwarded-For {remote_host} header_up X-Real-IP {remote_host} }
-
-
Logging hygiene:
- Avoid running with overly verbose logging in production.
- The API avoids logging raw prompts and filesystem paths by default, but you should still treat logs as sensitive (they can contain error details and operational metadata).
Next Steps
- Set up CI/CD pipeline for automated builds
- Configure monitoring and logging
- Set up backup strategy for persistent data
- Consider using a reverse proxy (nginx, traefik) for production
VM Setup (Ubuntu)
If you're setting up Docker on a fresh Ubuntu VM (20.04+), follow these steps.
Prerequisites
- Ubuntu VM with sudo privileges
- Internet access
Install Docker Engine
Update packages and install dependencies:
sudo apt update && sudo apt upgrade -y sudo apt install -y ca-certificates curl gnupg lsb-release
Add Docker's official GPG key:
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Set up the Docker repository:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker:
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Post-Install Configuration
Allow non-root Docker usage:
sudo usermod -aG docker $USER
Log out and back in, or run newgrp docker for the change to take effect.
Verify installation:
docker version
docker run hello-world