A comprehensive Python CLI tool that creates isolated development environments using Git worktrees, Docker Compose, and Caddy reverse proxy. Each feature branch gets its own complete environment with isolated databases, Redis, and media storage.
# Install via pip
pip install dockertree
# Install with MCP server support
pip install dockertree[mcp]
# Or install from source
git clone https://github.com/catalpainternational/dockertree.git
cd dockertree
pip install -e .
# Or install from source with MCP support
pip install -e .[mcp]# Initialize dockertree in your project
dockertree setup
# Start global Caddy proxy
dockertree start-proxy
# Create and start a worktree
dockertree create feature-auth
dockertree feature-auth up -d
# Access your isolated environment (note: domain includes project name)
# If your project is named "myapp", the URL will be:
open http://myapp-feature-auth.localhost
# Export environment as shareable package (includes code by default)
dockertree packages export feature-auth
# Import environment from package
dockertree packages import myapp-feature-auth-2024-01-15.dockertree-package.tar.gz
# Stop and clean up
dockertree feature-auth down
dockertree remove feature-authDockertree CLI provides complete environment isolation through:
- Git Worktrees: Isolated working directories for each branch
- Docker Compose: Isolated containers with branch-specific volumes
- Caddy Proxy: Dynamic routing based on branch names
- Volume Isolation: Branch-specific databases, Redis, and media storage
- Package Management: Export/import complete environments as shareable packages
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Global Caddy │ │ Worktree A │ │ Worktree B │
│ (Port 80) │ │ feature-auth │ │ feature-pay │
│ │ │ │ │ │
│ Routes to: │───▶│ • PostgreSQL │ │ • PostgreSQL │
│ • *.localhost │ │ • Redis │ │ • Redis │
│ │ │ • Web App │ │ • Web App │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Important: Dockertree uses a worktree-name-first pattern. For up and down commands, the syntax is dockertree <worktree_name> up and dockertree <worktree_name> down, not dockertree up <worktree_name>.
| Command | Description | Example |
|---|---|---|
setup |
Initialize dockertree for this project | dockertree setup [--monkey-patch] |
| Command | Description | Example |
|---|---|---|
start-proxy |
Start global Caddy proxy container | dockertree start-proxy |
stop-proxy |
Stop global Caddy proxy container | dockertree stop-proxy |
start |
Alias for start-proxy | dockertree start |
stop |
Alias for stop-proxy | dockertree stop |
| Alias | Full Command | Description |
|---|---|---|
-D |
delete |
Delete worktree and branch completely |
-r |
remove |
Remove worktree but keep git branch |
| Command | Description | Example |
|---|---|---|
create <branch> |
Create worktree | dockertree create feature-auth |
<branch> up -d |
Start worktree environment | dockertree feature-auth up -d |
<branch> down |
Stop worktree environment | dockertree feature-auth down |
remove <branch> |
Remove worktree (keep branch) | dockertree remove feature-auth |
delete <branch> |
Delete worktree and branch | dockertree delete feature-auth |
Important: Branch names must match exactly. The delete and remove commands check worktrees, branches, and docker volumes for exact matches only. If no exact match is found, an error message will show what was checked.
Dockertree supports direct passthrough to Docker Compose commands using the pattern: dockertree <worktree_name> <compose-command>
| Command Pattern | Description | Example |
|---|---|---|
<branch> exec <service> <cmd> |
Execute command in container | dockertree feature-auth exec web python manage.py migrate |
<branch> logs <service> |
View container logs | dockertree feature-auth logs web |
<branch> ps |
List containers | dockertree feature-auth ps |
<branch> run <service> <cmd> |
Run one-off command | dockertree feature-auth run --rm web python manage.py test |
<branch> build |
Build services | dockertree feature-auth build |
<branch> restart <service> |
Restart service | dockertree feature-auth restart web |
<branch> <compose-cmd> |
Any docker compose command | dockertree feature-auth pull, dockertree feature-auth config |
Supported Docker Compose Commands:
exec,logs,ps,run,build,pull,push,restartstart,stop,up,down,config,images,porttop,events,kill,pause,unpause,scale
| Command | Description | Example |
|---|---|---|
remove <pattern> |
Remove worktrees matching pattern | dockertree remove test-* |
delete <pattern> |
Delete worktrees and branches matching pattern | dockertree delete feature-* |
Wildcard Patterns:
*- Matches any characters (e.g.,test-*matchestest-feature,test-bugfix)?- Matches single character (e.g.,test-?matchestest-1,test-a)[abc]- Matches any character in brackets (e.g.,test-[abc]matchestest-a,test-b,test-c)- Case-insensitive matching (e.g.,
test-*matchesTest-Feature,TEST-FEATURE)
| Command | Description | Example |
|---|---|---|
remove-all |
Remove all worktrees (keep branches) | dockertree remove-all |
delete-all |
Delete all worktrees and branches | dockertree delete-all --force |
| Command | Description | Example |
|---|---|---|
list |
List active worktrees | dockertree list |
prune |
Remove prunable worktrees | dockertree prune |
help |
Show help information | dockertree help |
clean-legacy |
Clean legacy dockertree elements | dockertree clean-legacy |
| Command | Description | Example |
|---|---|---|
volumes list |
List all worktree volumes | dockertree volumes list |
volumes size |
Show volume sizes | dockertree volumes size |
volumes backup <branch> |
Backup worktree volumes | dockertree volumes backup feature-auth |
volumes restore <branch> <file> |
Restore from backup | dockertree volumes restore feature-auth backup.tar |
volumes clean <branch> |
Clean up volumes | dockertree volumes clean feature-auth |
| Command | Description | Example |
|---|---|---|
droplet create [branch_name] |
Create a new Digital Ocean droplet and push environment (default). Droplet name auto-detected from domain subdomain or branch name | dockertree droplet create or dockertree droplet create test |
droplet create [branch_name] --create-only |
Create a droplet only, do not push | dockertree droplet create test --create-only |
droplet create --domain app.example.com |
Create droplet using subdomain from domain as name | dockertree droplet create --domain app.example.com |
droplet list |
List all droplets (formatted table) | dockertree droplet list |
droplet list --as-json |
List droplets as JSON | dockertree droplet list --as-json |
droplet list --as-csv |
List droplets as CSV | dockertree droplet list --as-csv |
droplet regions |
List available Digital Ocean regions | dockertree droplet regions |
droplet regions --show-all |
Include unavailable regions in region list | dockertree droplet regions --show-all |
droplet info <id> |
Get droplet information | dockertree droplet info 12345678 |
droplet destroy <id> |
Destroy a droplet (supports ID or name, comma-separated) | dockertree droplet destroy 12345678 or dockertree droplet destroy my-droplet or dockertree droplet destroy 123,456,789 |
droplet push [<branch>] <scp_target_or_droplet> |
Push dockertree package to remote server (supports progressive SCP patterns) | dockertree droplet push feature-auth user@server:/path or dockertree droplet push feature-auth my-app |
Droplet Creation Options:
branch_name(optional argument) - Branch/worktree name (auto-detected from current directory if not provided)--region <region>- Droplet region (defaults from env or nyc1)--size <size>- Droplet size (defaults from env or s-1vcpu-1gb)--image <image>- Droplet image (defaults from env or ubuntu-22-04-x64)--ssh-keys <key>- SSH key IDs or fingerprints (can be specified multiple times)--tags <tag>- Tags for the droplet (can be specified multiple times)--wait- Wait for droplet to be ready--api-token <token>- Digital Ocean API token (or use DIGITALOCEAN_API_TOKEN env var)--create-only- Only create droplet, do not push environment (default: creates and pushes)--scp-target <target>- SCP target (optional, defaults to root@:/root)--vpc-uuid <uuid>- VPC UUID for the droplet (if not provided, uses default VPC for the region)--central-droplet-name <name>- Name of central droplet to reuse VPC UUID from (for worker deployments)- Push options:
--no-auto-import(opt-out),--prepare-server,--domain,--ip,--dns-token,--skip-dns-check,--resume,--code-only
Droplet Name Auto-Detection:
- If
--domainis provided: uses subdomain as droplet name (e.g.,app.example.com→app) - Otherwise: uses branch/worktree name as droplet name (auto-detected if not provided)
Droplet List Options:
--as-jsonor--json- Output as JSON format--as-csv- Output as CSV format--api-token <token>- Digital Ocean API token (or use DIGITALOCEAN_API_TOKEN env var)
Droplet Regions Options:
--show-all- Include unavailable regions--as-jsonor--json- Output as JSON format--as-csv- Output as CSV format--api-token <token>- Digital Ocean API token (or use DIGITALOCEAN_API_TOKEN env var)
Droplet Destroy Options:
<id>- Single droplet ID or name, or comma-separated list of IDs/names (e.g.,123,456,789ormy-droplet,another-droplet,123)--force- Skip confirmation (destroys without typing droplet name)--only-droplet- Only destroy droplet, skip DNS deletion--only-domain- Only destroy DNS records, skip droplet deletion--domain <domain>- Domain name for DNS deletion (optional, auto-detects if not provided)--api-token <token>- Digital Ocean API token (or use DIGITALOCEAN_API_TOKEN env var)--dns-token <token>- DNS API token (if different from droplet token)--json- Output as JSON format
Droplet Destroy Behavior:
- Default: Destroys droplet only (backward compatible)
- Multiple Droplets: Accepts comma-separated IDs or names (e.g.,
123,456,789ormy-droplet,another-droplet,123) and processes all droplets sequentially - Name Resolution: Automatically resolves droplet names to IDs. If multiple droplets share the same name, use the droplet ID instead.
- Error Handling: Continues destroying remaining droplets even if one fails
- Summary Output: Shows summary of destroyed/failed droplets when processing multiple IDs
- Confirmation: Requires typing the exact droplet name to confirm (unless
--force) - each droplet requires confirmation separately - DNS Auto-detection: When destroying droplet, automatically finds and deletes DNS records pointing to droplet IP
- DNS-only mode: Use
--only-domainto delete DNS records without destroying the droplet - Domain confirmation: When deleting DNS records, requires typing the full domain name (e.g., "app.example.com") unless
--force - Domain override: Use
--domain <domain>to limit DNS search to specific domain
| Command | Description | Example |
|---|---|---|
packages export <branch> |
Export worktree as shareable package (includes code by default) | dockertree packages export feature-auth |
packages import <file> |
Import environment from package (auto-detects standalone mode) | dockertree packages import my-package.tar.gz |
packages import <file> --domain <sub.domain.tld> |
Import with domain override (HTTPS via Caddy) | dockertree packages import pkg.tar.gz --domain myapp.example.com |
packages import <file> --ip <x.x.x.x> |
Import with IP override (HTTP-only) | dockertree packages import pkg.tar.gz --ip 203.0.113.10 |
packages import <file> --standalone |
Force standalone import (create new project) | dockertree packages import my-package.tar.gz --standalone --target-dir ./myproject |
packages list |
List available packages | dockertree packages list |
packages validate <file> |
Validate package integrity | dockertree packages validate my-package.tar.gz |
Dockertree intelligently detects whether you're in an existing project and automatically switches between normal and standalone import modes.
Automatic Detection (Default):
# In an empty directory - automatically creates new project
cd /path/to/new-location
dockertree packages import my-package.tar.gz
# Auto-detects: No git repo → standalone mode
# Creates: ./myproject-standalone/
# In existing dockertree project - automatically imports as worktree
cd /path/to/existing-project
dockertree packages import my-package.tar.gz
# Auto-detects: Existing .dockertree → normal mode
# Creates: new worktree in projectExplicit Standalone Mode:
# Force standalone import with custom directory
dockertree packages import my-package.tar.gz --standalone --target-dir ./my-new-project
# Force standalone even if in existing project
dockertree packages import my-package.tar.gz --standaloneRequirements:
- Standalone imports require packages exported with code (
--include-code, which is the default) - Normal imports work with or without code
What Gets Created in Standalone Mode:
- Complete project structure extracted from package
- Project root
.dockertree/configuration - Worktree with its own
.dockertree/(fractal structure) - All code files
- Docker volumes (if
--restore-data) - Ready-to-use isolated environment
Note: Standalone import is simple - it extracts the project tar and applies domain/IP overrides. No git initialization or additional setup needed since the package contains the complete fractal structure.
| Command | Description | Example |
|---|---|---|
completion install [shell] |
Install shell completion | dockertree completion install |
completion uninstall |
Remove shell completion | dockertree completion uninstall |
completion status |
Show completion status | dockertree completion status |
During dockertree setup, if a Django project is detected (manage.py present and a settings.py found), Dockertree validates your settings are environment-driven for reverse proxy use:
- ALLOWED_HOSTS (comma-separated)
- CSRF_TRUSTED_ORIGINS (space-separated)
- USE_X_FORWARDED_HOST (boolean)
- SECURE_PROXY_SSL_HEADER (tuple as
HTTP_X_FORWARDED_PROTO,https)
If any are missing, Dockertree prints a nicely formatted guidance block and, when --monkey-patch is supplied, appends a safe snippet to settings.py to read these values from environment variables.
Suggested snippet (auto-added with --monkey-patch):
import os
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED_ORIGINS", "").split()
USE_X_FORWARDED_HOST = os.getenv("USE_X_FORWARDED_HOST", "False") == "True"
_hdr = os.getenv("SECURE_PROXY_SSL_HEADER")
if _hdr:
SECURE_PROXY_SSL_HEADER = tuple(_hdr.split(",", 1))When using SQLite in Docker, store the database file on a named Docker volume so Dockertree export/import captures it (just like Postgres/Redis volumes):
- In Django settings, point SQLite to a path under a data mount, e.g.
/data/db.sqlite3(or use an env var likeSQLITE_PATH=/data/db.sqlite3). - In your compose, mount a named volume for the web service:
services:
web:
volumes:
- sqlite_data:/data
volumes:
sqlite_data: {}Dockertree will back up and restore sqlite_data with other volumes, preserving DB state across export/import. If you keep the DB at /app/db.sqlite3 (bind-mounted source tree), it is not part of any Docker volume snapshot and will not be included.
When you run dockertree setup, it creates a .dockertree/ directory with:
config.yml- Project configurationdocker-compose.worktree.yml- Transformed compose file (generated from existing docker-compose.yml)Caddyfile.dockertree- Caddy configurationREADME.md- User guide optimized for coding agentsworktrees/- Worktree directories
Droplet defaults can be configured in .env or .dockertree/env.dockertree:
# .env or .dockertree/env.dockertree
DROPLET_DEFAULT_REGION=nyc1
DROPLET_DEFAULT_SIZE=s-1vcpu-1gb
DROPLET_DEFAULT_IMAGE=ubuntu-22-04-x64
# SSH key names (comma-separated, e.g., anders,peter)
# Only key names are supported, not numeric IDs or fingerprints
DROPLET_DEFAULT_SSH_KEYS=anders,peter
DIGITALOCEAN_API_TOKEN=your_token_hereThe .dockertree/config.yml file contains:
project_name: myproject
caddy_network: dockertree_caddy_proxy
worktree_dir: worktrees
services:
web:
container_name_template: ${COMPOSE_PROJECT_NAME}-web
db:
container_name_template: ${COMPOSE_PROJECT_NAME}-db
redis:
container_name_template: ${COMPOSE_PROJECT_NAME}-redis
volumes:
- postgres_data
- redis_data
- media_files
environment:
DEBUG: "True"
ALLOWED_HOSTS: "localhost,127.0.0.1,*.localhost,web"Worktree-specific volumes follow the pattern: {branch_name}_{volume_type}
feature-auth_postgres_data- Database datafeature-auth_redis_data- Cache datafeature-auth_media_files- User uploads
- Global Network:
dockertree_caddy_proxy(external) - Worktree Networks:
{branch_name}_internal,{branch_name}_web
# 1. Initialize dockertree in your project
dockertree setup
# 2. Start global infrastructure
dockertree start-proxy
# 3. Create feature branch environment
dockertree create feature-new-auth
dockertree feature-new-auth up -d
# 4. Develop and test
open http://feature-new-auth.localhost
# Make changes, test database migrations, etc.
# 5. Clean up when done
dockertree feature-new-auth down
dockertree remove feature-new-auth# Work on multiple features simultaneously
dockertree create feature-auth
dockertree create feature-payments
dockertree create feature-notifications
# Start all environments
dockertree feature-auth up -d
dockertree feature-payments up -d
dockertree feature-notifications up -d
# Access each independently
open http://feature-auth.localhost
open http://feature-payments.localhost
open http://feature-notifications.localhost# Test database migrations in isolation
dockertree create test-migration
dockertree test-migration up -d
# Run migrations, test data changes
# Each worktree has its own database
# Clean up test environment
dockertree delete test-migration# Execute Django management commands
dockertree feature-auth exec web python manage.py migrate
dockertree feature-auth exec web python manage.py collectstatic
# View application logs
dockertree feature-auth logs web
dockertree feature-auth logs -f web # Follow logs
# Run one-off commands
dockertree feature-auth run --rm web python manage.py test
dockertree feature-auth run --rm web bash
# Check container status
dockertree feature-auth ps
# Restart services
dockertree feature-auth restart webDocker not running
Error: Docker is not running. Please start Docker and try again.Solution: Start Docker Desktop or Docker daemon
PostgreSQL Database Corruption
Error: invalid primary checkpoint record
Error: could not locate a valid checkpoint recordSolution: This has been fixed! Dockertree now stops the original database container before copying volumes, then restarts it after copying. This ensures data consistency and prevents corruption.
Worktree already exists
Error: Worktree for branch 'feature-auth' already existsSolution: Use dockertree list to see existing worktrees, or remove the existing one
Port conflicts
Error: Port 80 is already in useSolution: Stop other services using port 80, or check if global Caddy is already running
Volume creation fails
Error: Failed to create volumeSolution: Check Docker disk space, ensure Docker has sufficient permissions
Branch not found during deletion
Error: No exact match found for 'feature-auth'. Checked: worktrees, branches, and docker volumes.Solution: Use dockertree list to see exact branch names. Branch names are case-sensitive and must match exactly.
HTTPS hangs or certificate acquisition fails (Let's Encrypt rate limit)
Error: HTTP 429 urn:ietf:params:acme:error:rateLimited - too many certificates (5) already issued for this exact set of identifiers in the last 168h0m0sSymptoms: HTTPS connections hang or timeout, HTTP works fine. Browser shows connection timeout when accessing https://your-domain.com.
Root Cause: Let's Encrypt limits certificate issuance to 5 certificates per domain per 168 hours (7 days). If you've deployed the same domain multiple times, you may hit this limit.
Solution: Use Let's Encrypt staging certificates temporarily until the rate limit expires:
-
Manual Fix (Immediate): SSH to your server and update Caddy configuration:
# Get current config curl -s http://localhost:2019/config/ > /tmp/caddy_config.json # Edit config to use staging endpoint (add "ca" field to issuer) # Update the issuer in apps.tls.automation.policies[0].issuers[0]: # Add: "ca": "https://acme-staging-v02.api.letsencrypt.org/directory" # Reload Caddy curl -X POST http://localhost:2019/load -H "Content-Type: application/json" -d @/tmp/caddy_config.json
-
Automatic Fix (Long-term): Dockertree's dynamic configuration script (
caddy-dynamic-config.py) now automatically detects rate limit errors and falls back to staging certificates. The script checks Caddy logs for rate limit patterns and automatically switches to staging when detected. -
Force Staging Mode for Testing: You can force staging certificates for testing without hitting rate limits by setting the
USE_STAGING_CERTIFICATESenvironment variable:# Option 1: Set in your shell before running dockertree commands export USE_STAGING_CERTIFICATES=1 # Option 2: Add to your .dockertree/env.dockertree file (recommended for droplet create/push) echo "USE_STAGING_CERTIFICATES=1" >> .dockertree/env.dockertree
For
droplet createanddroplet push: AddUSE_STAGING_CERTIFICATES=1to your.dockertree/env.dockertreefile before exporting. This ensures the setting is included in the package and will be used on the remote server. This is perfect for testing environments where you don't want to consume your production certificate quota. Staging certificates don't count against Let's Encrypt's rate limits. -
Staging Certificates:
- Staging certificates work for HTTPS but show browser security warnings
- Users can proceed after accepting the warning
- Connection is still encrypted, just not trusted by default
- Switch back to production certificates after rate limit expires (check Caddy logs for "retry after" timestamp)
- Staging certificates don't count against Let's Encrypt rate limits - perfect for testing!
-
Monitor Certificate Health: The
caddy-docker-monitor.pyscript monitors certificate health and logs warnings when rate limits are detected.
Note: Rate limits expire after 168 hours (7 days) from the first certificate issuance. Check Caddy logs for the exact expiration time.
# Check Docker status
docker ps
# Check worktree status
dockertree list
# Check volume status
dockertree volumes list
# Check network status
docker network ls | grep dockertree_caddy_proxy# View container logs
docker logs dockertree_caddy_proxy
docker logs {branch-name}-web
# Check worktree directory
ls -la worktrees/{branch-name}/
# Verify environment files
cat worktrees/{branch-name}/.dockertree/env.dockertreeDockertree CLI provides intelligent tab completion for Bash and Zsh shells, making it faster and easier to use.
- Command Completion: Tab complete all dockertree commands and subcommands
- Worktree Names: Auto-complete worktree branch names for commands that need them
- Volume Operations: Complete worktree names for volume backup/restore/clean operations
- Flag Completion: Auto-complete command flags like
--force,-d,--detach
Shell completion is automatically offered during project setup:
dockertree setup
# After successful setup, you'll be prompted:
# "Would you like to install bash completion? [Y/n]"# Install for current shell (auto-detected)
dockertree completion install
# Install for specific shell
dockertree completion install bash
dockertree completion install zsh
# Check installation status
dockertree completion status
# Uninstall completion
dockertree completion uninstall# Tab complete commands
dockertree <TAB>
# Shows: create delete down help list prune remove remove-all setup start-proxy stop-proxy start stop up volumes
# Tab complete worktree names
dockertree feature-auth <TAB>
# Shows: up down exec logs ps run build restart
# Tab complete volume operations
dockertree volumes backup <TAB>
# Shows: feature-auth feature-payments feature-notifications
# Tab complete flags
dockertree delete <TAB>
# Shows: feature-auth --forceCompletion not working after installation:
# Restart your shell or source the configuration
source ~/.bashrc # For Bash
source ~/.zshrc # For ZshCheck completion status:
dockertree completion statusReinstall completion:
dockertree completion uninstall
dockertree completion installDockertree includes a Model Context Protocol (MCP) server that enables AI assistants like Claude and Cursor to manage isolated development environments programmatically.
# Install with MCP dependencies
pip install dockertree[mcp]
# Or install from source with MCP support
git clone https://github.com/catalpainternational/dockertree.git
cd dockertree
pip install -e .[mcp]# Run the MCP server
dockertree-mcp
# Or run directly
python -m dockertree_mcp.serverThe MCP server enables AI assistants to:
- Create and manage worktrees:
create_worktree,start_worktree,stop_worktree - Manage volumes:
backup_volumes,restore_volumes,clean_volumes - Control proxy:
start_proxy,stop_proxy,get_proxy_status - Get information:
list_worktrees,get_worktree_info,list_volumes
Add to your Claude Desktop configuration:
{
"mcpServers": {
"dockertree": {
"command": "dockertree-mcp",
"args": [],
"env": {
"DOCKERTREE_WORKING_DIR": "/path/to/your/project"
}
}
}
}Critical: Always provide the working_directory parameter when using MCP tools. This tells dockertree which project to operate on.
# ✅ CORRECT: Always specify working_directory
create_worktree({
"branch_name": "feature-auth",
"working_directory": "/Users/ders/kenali/blank" # Target project directory
})
# ❌ INCORRECT: Missing working_directory
create_worktree({"branch_name": "feature-auth"}) # Will use MCP server's directoryFor detailed MCP server documentation, see mcp/README.md.
Create custom environment files for specific worktrees:
# Create worktree with custom settings
dockertree create feature-auth
# Edit environment file
vim worktrees/feature-auth/.dockertree/env.dockertree
# Start with custom configuration
dockertree feature-auth up -d# Backup before major changes
dockertree volumes backup feature-auth
# Check volume sizes
dockertree volumes size
# Clean up old volumes
dockertree volumes clean feature-authAuto-Detection (recommended):
# Automatically chooses correct mode based on current directory
dockertree packages import myapp-feature-auth.tar.gzStandalone Import (create new project):
# Create new project from package
mkdir ~/new-project && cd ~/new-project
dockertree packages import myapp-feature-auth.tar.gz --standalone
# Or specify target directory
dockertree packages import myapp-feature-auth.tar.gz --standalone --target-dir ./myprojectNormal Import (add to existing project):
# Import to existing project as new worktree
cd /path/to/existing-project
dockertree packages import myapp-feature-auth.tar.gzImport Options:
# Import without data (configuration and code only)
dockertree packages import myapp-feature-auth.tar.gz --no-data
# Import to specific branch name
dockertree packages import myapp-feature-auth.tar.gz --target-branch new-branch-name
# Domain/IP overrides for deployment
dockertree packages import myapp-feature-auth.tar.gz --standalone --domain myapp.example.com # HTTPS via Caddy
dockertree packages import myapp-feature-auth.tar.gz --standalone --ip 203.0.113.10 # HTTP-only (no TLS)Export Options:
# Export complete environment as shareable package (includes code by default)
dockertree packages export feature-auth
# Export environment without code (smaller package)
dockertree packages export feature-auth --no-code
# Export to specific directory
dockertree packages export feature-auth --output-dir ./exportsPackage Management:
# List available packages
dockertree packages list
# Validate package integrity
dockertree packages validate myapp-feature-auth.tar.gz# Remove all worktrees (keeps git branches)
dockertree remove-all
# Delete all worktrees and branches (destructive)
dockertree delete-all --force
# Clean up prunable worktrees
dockertree prune# Remove all test branches (keeps git branches)
dockertree remove test-*
# Delete all feature branches and their worktrees
dockertree delete feature-*
# Remove branches matching pattern with confirmation
dockertree remove bugfix-*
# Output: Found 3 matching branch(es): bugfix-auth, bugfix-payment, bugfix-ui
# Remove 3 worktree(s) (keep branches)? [Y/n]:
# Delete with force flag (skips confirmation)
dockertree delete temp-* --force- 📖 Full Documentation Site - Complete documentation with search and navigation
- Project Setup Guide - Best practices for configuring your project for Dockertree
- Architecture Guide - Detailed system architecture and component relationships
- Troubleshooting Guide - Common issues and solutions
- Design Documents - Original design and implementation plans
- Test Documentation - Comprehensive test suite and coverage
- Configuration Reference - All configuration files and options
The dockertree CLI follows a modular architecture designed for easy extension:
- Core Infrastructure:
core/- Docker, Git, and environment management - Command Layer:
commands/- Business logic for each command - Utilities:
utils/- Shared functionality and validation - Configuration:
config/- Settings and Docker Compose files
- Create command class in
commands/ - Add CLI interface in
cli.py - Add tests in
tests/unit/ - Update documentation
- New Volume Types: Modify
EnvironmentManager.get_volume_names() - Custom Networks: Update
DockerManager.create_network() - Additional Validation: Extend
utils/validation.py
- Worktree Creation: ~30 seconds
- Volume Copying: ~60 seconds (depending on data size)
- Container Startup: ~45 seconds
- Memory Usage: ~1GB per worktree
- Disk Usage: ~5GB per worktree (varies with data)
Production Ready: All tests passing, 100% functional compatibility with original bash script.
Key Features:
- ✅ Complete environment isolation
- ✅ Dynamic routing with Caddy
- ✅ Volume management and backup
- ✅ Git worktree integration
- ✅ Docker Compose commands
- ✅ Comprehensive error handling
- ✅ Rich console output
- ✅ Type safety throughout
- ✅ Project-agnostic setup
- ✅ User guide in each .dockertree directory
- ✅ MCP server for AI assistant integration
- ✅ JSON output mode for programmatic access
- ✅ Push command for SCP-based deployment with auto-import enabled by default
# Create a new Digital Ocean droplet and automatically push environment to it
# Branch name auto-detected from current directory, droplet name uses branch name
# Auto-import is enabled by default
dockertree droplet create \
--prepare-server
# With explicit branch name (droplet name = branch name)
dockertree droplet create test \
--prepare-server
# With domain (droplet name = subdomain from domain)
dockertree droplet create \
--domain app.example.com \
--prepare-server
# With custom droplet configuration
dockertree droplet create test \
--region sfo3 --size s-2vcpu-4gb \
--prepare-server \
--domain app.example.com
# Create droplet only, do not push
dockertree droplet create test --create-only# Export and transfer a package to a remote server via SCP
# Supports progressive SCP target patterns:
dockertree droplet push feature-auth user@server:/var/dockertree/packages
# Progressive patterns - Username + domain
dockertree droplet push feature-auth [email protected]
# Progressive patterns - Domain + path
dockertree droplet push feature-auth example.com:/var/dockertree
# Progressive patterns - Domain only (resolves to IP)
dockertree droplet push feature-auth example.com
# Progressive patterns - IP + path
dockertree droplet push feature-auth 192.168.1.100:/var/dockertree
# Progressive patterns - IP only
dockertree droplet push feature-auth 192.168.1.100
# Droplet by name
dockertree droplet push feature-auth my-app
# Droplet by ID
dockertree droplet push feature-auth 12345678# Push with automatic DNS management and HTTPS deployment
# Auto-import is enabled by default
dockertree droplet push feature-auth user@server:/var/dockertree/packages \
--prepare-server \
--domain app.example.com \
--dns-token $DIGITALOCEAN_API_TOKEN
# Or use environment variable for token
export DIGITALOCEAN_API_TOKEN=your_token_here
dockertree droplet push feature-auth user@server:/var/dockertree/packages \
--domain app.example.com
# Or add to .env file in project root (no export needed)
# .env file:
# DIGITALOCEAN_API_TOKEN=your_token_here
dockertree droplet push feature-auth user@server:/var/dockertree/packages \
--domain app.example.com
# To skip auto-import (only push, don't import/start):
dockertree droplet push feature-auth user@server:/var/dockertree/packages \
--no-auto-import
# Options:
# --domain myapp.example.com # Domain for HTTPS deployment
# --dns-token <token> # Digital Ocean API token (or use DIGITALOCEAN_API_TOKEN env var)
# --skip-dns-check # Skip DNS validation
# --ip 203.0.113.10 # IP-only HTTP mode (no Let's Encrypt for IPs)# Push code-only update to pre-existing server
# Uses stored push configuration from env.dockertree (saved after first push)
dockertree droplet push --code-only
# Code-only update with explicit arguments (overrides stored config)
# Works with all progressive SCP patterns:
dockertree droplet push feature-auth user@server:/var/dockertree/packages --code-only
dockertree droplet push feature-auth my-app --code-only
dockertree droplet push feature-auth 192.168.1.100 --code-only
dockertree droplet push feature-auth [email protected] --code-only
dockertree droplet push feature-auth example.com:/var/dockertree --code-only
# Code-only update with domain/IP override
dockertree droplet push --code-only --domain app.example.com
dockertree droplet push --code-only --ip 203.0.113.10How Code-Only Push Works:
- Automatic Detection: Dockertree automatically detects whether your code is stored in Docker volumes or bind mounts
- Volume-Based: If code is in volumes, only code volumes are backed up and transferred
- Archive-Based: If code is in bind mounts, a git archive is created and extracted to the worktree
- Configuration Storage: Push configuration (scp_target, branch_name, domain, ip) is automatically saved to
.dockertree/env.dockertreeafter successful push - Reuse Configuration: On subsequent pushes, stored configuration is used automatically (CLI arguments override stored config)
Push Configuration Variables:
After a successful push (full or code-only), the following variables are saved to .dockertree/env.dockertree:
PUSH_SCP_TARGET=user@server:/path/to/packages
PUSH_BRANCH_NAME=feature-auth
PUSH_DOMAIN=app.example.com # optional
PUSH_IP=203.0.113.10 # optional (mutually exclusive with domain)
When to Use Code-Only Push:
- Quick code updates on pre-existing servers
- Deploying minor fixes without full environment redeployment
- Faster iteration during development
- When only code changes, not environment configuration
When to Use Full Push:
- Initial deployment
- Environment configuration changes
- Database schema migrations
- Volume data updates
The droplet push command supports progressive SCP target patterns, making it easier to deploy without manually constructing SCP targets:
Supported Patterns:
- Full SCP target:
user@server:/path→ use as-is (backward compatible) - Username + domain:
[email protected]→ resolves domain to IP, constructsuser@<ip>:/root - Domain + path:
example.com:/path→ resolves domain to IP, constructsroot@<ip>:<path> - Domain only:
example.com→ resolves domain to IP, constructsroot@<ip>:/root - IP + path:
192.168.1.100:/path→ constructs[email protected]:/path - IP only:
192.168.1.100→ constructs[email protected]:/root - Droplet ID:
12345678→ resolves to IP via API, constructsroot@<ip>:/root - Droplet name:
my-app→ resolves to IP via API, constructsroot@<ip>:/root
Resolution Priority:
- If already an IP address → use directly
- Try DNS resolution (domain lookup) → use resolved IP
- Try droplet ID/name lookup → use droplet IP
Domain Setup Fix:
When using progressive patterns with --domain, the resolved IP from the SCP target is automatically used for DNS management, ensuring DNS records point to the correct server.
Dockertree supports deploying multiple independent projects to the same server, each with their own unique URL. This enables efficient resource usage while maintaining complete isolation between projects.
Global Caddy Proxy:
- A single global Caddy container (
dockertree_caddy_proxy) runs on the server - Monitors all Docker containers on the host via
/var/run/docker.sock - Automatically discovers containers with
caddy.proxylabels from any project - Routes traffic based on domain/IP specified in container labels
Container Discovery:
- Caddy dynamically scans all containers on the Docker host
- Finds containers with
caddy.proxylabels regardless of which project directory they belong to - Each project's containers are automatically discovered and routed
Shared Network:
- All project containers join the
dockertree_caddy_proxynetwork (external) - This enables the global Caddy to route to containers from any project
- Projects remain isolated but share the routing infrastructure
# Deploy Project 1 (e.g., Django app) to server
cd /var/dockertree/project1
dockertree droplet push feature-auth user@server:/var/dockertree/packages \
--domain app1.example.com
# Deploy Project 2 (e.g., Flask API) to same server
cd /var/dockertree/project2
dockertree droplet push api-v2 user@server:/var/dockertree/packages \
--domain api.example.com
# Deploy Project 3 (e.g., Node.js app) to same server
cd /var/dockertree/project3
dockertree droplet push main user@server:/var/dockertree/packages \
--domain app3.example.comAll three projects will:
- ✅ Run independently with isolated containers and volumes
- ✅ Be accessible via their own unique domains
- ✅ Share the same global Caddy proxy for routing
- ✅ Not interfere with each other
-
Global Caddy Must Be Running: Ensure
dockertree_caddy_proxycontainer is running on the server# On the server, start global Caddy if not already running dockertree start-proxy -
Unique Domains/IPs: Each project must have a unique domain or IP address
- ✅
app1.example.com,app2.example.com,api.example.com(all different) - ❌ Cannot use the same domain for multiple projects
- ✅
-
Project Isolation: Projects can be in completely different directories
- Standalone mode works perfectly for multi-project deployments
- Each project maintains its own
.dockertree/configuration
-
Automatic Label Configuration: Dockertree automatically adds
caddy.proxylabels to containers- Labels are set based on
--domainor--ipflags during push - No manual configuration needed
- Labels are set based on
- Resource Efficiency: One reverse proxy handles all projects
- Complete Isolation: Each project has its own containers, volumes, and networks
- Simple Management: Deploy each project independently
- Flexible: Mix domain-based HTTPS and IP-based HTTP deployments
- Scalable: Add new projects without affecting existing ones
┌─────────────────────────────────────────────────────────┐
│ Global Caddy Proxy │
│ (dockertree_caddy_proxy) │
│ Ports: 80, 443, 2019 │
│ │
│ Monitors ALL containers via Docker socket │
│ Routes based on caddy.proxy labels │
└─────────────────────────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
┌───────▼──────┐ ┌──▼──────┐ ┌──▼──────┐
│ Project 1 │ │Project 2│ │Project 3│
│ │ │ │ │ │
│ app1.example │ │api.exam │ │app3.exam│
│ .com │ │ ple.com│ │ ple.com│
│ │ │ │ │ │
│ • PostgreSQL │ │• MongoDB│ │• Redis │
│ • Redis │ │• Web │ │• Web │
│ • Web App │ │• API │ │• App │
└──────────────┘ └─────────┘ └─────────┘
Dockertree can automatically manage DNS records via Digital Ocean DNS API:
- Automatic Domain Creation: If a subdomain doesn't exist, dockertree will automatically create it when
--domainis provided - Domain Validation: Checks if DNS records already exist and point to the correct server
- Supported Provider: Digital Ocean DNS
- API Token Configuration: Multiple options supported (priority order):
- CLI flag:
--dns-token <token> - Shell environment:
export DIGITALOCEAN_API_TOKEN=token .envfile: AddDIGITALOCEAN_API_TOKEN=tokento project root.envfile- Global config: Add
DIGITALOCEAN_API_TOKEN=tokento~/.dockertree/env.dockertreefile
- CLI flag:
- Generate a personal access token from https://cloud.digitalocean.com/account/api/tokens
- Configure token using one of these methods:
- Shell environment (recommended for CI/CD):
export DIGITALOCEAN_API_TOKEN=your_token - Project
.envfile (recommended for project-specific tokens): AddDIGITALOCEAN_API_TOKEN=your_tokento your project root.envfile - Global config (recommended for personal tokens): Add
DIGITALOCEAN_API_TOKEN=your_tokento~/.dockertree/env.dockertreefile - CLI flag: Use
--dns-token <token>when pushing
- Shell environment (recommended for CI/CD):
When dockertree creates a DNS record, it is immediately available on Digital Ocean's authoritative nameservers, but it takes time to propagate to all DNS resolvers worldwide.
What is DNS Propagation? DNS propagation is the time it takes for DNS record changes to spread across all DNS servers on the internet. When you create a new DNS record, it's immediately available on the authoritative nameservers (Digital Ocean's in this case), but other DNS resolvers (like Google's 8.8.8.8 or Cloudflare's 1.1.1.1) cache DNS records and may take time to update.
Expected Propagation Times:
- Authoritative nameservers: Immediate (Digital Ocean nameservers)
- Public resolvers: 5-60 minutes typically
- Global propagation: Up to 48 hours in rare cases
Verifying DNS Records:
You can verify DNS records in several ways:
-
Check on Digital Ocean nameservers (immediate):
dig your-domain.com A @ns1.digitalocean.com dig your-domain.com A @ns2.digitalocean.com dig your-domain.com A @ns3.digitalocean.com
-
Check on public resolvers (may take time):
dig your-domain.com A @8.8.8.8 # Google DNS dig your-domain.com A @1.1.1.1 # Cloudflare DNS
-
Check from your local machine:
dig your-domain.com A nslookup your-domain.com
Troubleshooting:
- If the record exists on Digital Ocean nameservers but not on public resolvers, wait a few more minutes
- If the record doesn't exist on any nameserver, check that the DNS record was created successfully
- Browser DNS caches may need to be cleared or wait for TTL expiration
Dockertree supports VPC (Virtual Private Cloud) deployments for secure private networking between droplets:
Central and Worker Droplet Setup:
# 1. Create central server droplet with db/redis services
dockertree droplet create test \
--domain central.example.com \
--containers test.db,test.redis,test.web \
--prepare-server
# 2. Create worker droplet in same VPC (reuses VPC UUID from central)
dockertree droplet create test \
--central-droplet-name central \
--containers test.rq-worker-1 \
--exclude-deps db,redis \
--prepare-serverVPC Features:
- Automatic VPC Detection: Extracts private IP addresses and VPC UUID from droplets
- VPC UUID Reuse: Use
--central-droplet-nameto automatically reuse VPC UUID from central droplet - Port Binding: Configure
.dockertree/config.ymlto automatically bind ports for VPC-accessible services:vpc: auto_bind_ports: true # Enable automatic port binding (default: false) bind_to_private_ip: true # Bind to private IP instead of 0.0.0.0 (default: true, recommended for security)
- Security: Redis and database ports are automatically bound to private IP (not public) when available, preventing public internet exposure
- Firewall Configuration: Optional automatic UFW firewall rules to restrict access to VPC network only
- Worker Environment Configuration: When deploying workers with
--exclude-deps, environment variables are automatically configured to point to central server's private IP - VPC Metadata: Package metadata includes VPC deployment information for automatic configuration
VPC Configuration Options:
--vpc-uuid <uuid>- Explicitly specify VPC UUID--central-droplet-name <name>- Reuse VPC UUID from central droplet (for worker deployments)--exclude-deps <services>- Exclude services from dependency resolution (indicates worker deployment)
Security Best Practices for VPC Deployments:
When deploying with --central-server and VPC networking, dockertree implements multiple security layers:
-
Private IP Binding (Default: Enabled)
- Redis and database ports bind to private IP address (e.g.,
10.x.x.x:6379) instead of0.0.0.0 - Prevents public internet exposure while maintaining VPC accessibility
- Configure via
.dockertree/config.yml:vpc: bind_to_private_ip: true # Default: true
- Redis and database ports bind to private IP address (e.g.,
-
Firewall Rules (Optional: Opt-in)
- Automatically configures UFW firewall rules to restrict access to VPC network (10.0.0.0/8)
- Only allows connections from within the VPC
- Configure via
.dockertree/config.yml:vpc: auto_configure_firewall: true # Default: false (opt-in)
-
Verification After deployment, verify security:
# On central server, check port bindings docker ps --format "{{.Names}}\t{{.Ports}}" | grep -E "(redis|db)" # Should show: 10.x.x.x:6379->6379/tcp (not 0.0.0.0:6379) # Verify public access is blocked telnet <public-ip> 6379 # Should fail # Verify VPC access works telnet <private-ip> 6379 # Should succeed from worker droplet
- When using
--domain, dockertree automatically enables HTTPS via Caddy's Let's Encrypt integration - When using
--ip, deployments are HTTP-only. Certificate authorities do not issue certificates for IP addresses; use a domain for HTTPS. - DNS records are automatically created when
--domainis provided (use--skip-dns-checkto skip DNS management) - You can add defaults in
.dockertree/config.yml(optional):
deployment:
default_server: user@server:/var/dockertree/packages
default_domain: myapp.example.com
default_ip: 203.0.113.10
ssh_key: ~/.ssh/deploy_key
vpc:
auto_bind_ports: true # Enable automatic port binding for VPC-accessible services
bind_to_private_ip: true # Bind to private IP only (default: true, recommended for security)
auto_configure_firewall: false # Auto-configure UFW rules (default: false, opt-in)For detailed architecture information, see documentation/ARCHITECTURE.md
Additional Push Details:
- Auto-import uses a robust remote script with strict mode and consistent quoting to avoid empty variables and broken chains.
- The remote script resolves the dockertree binary (prefers
/opt/dockertree-venv/bin/dockertree, falls back todockertree). - Import runs with
--non-interactive; older dockertree versions safely ignore unknown flags. - If an existing dockertree project is detected on the server, a normal import is used; otherwise standalone import is performed.