This guide covers the deployment and management of the DDEV Coder template for administrators.
Before deploying this template, ensure the following are in place:
- Coder v2+ server installed and running
- Administrative access to Coder
- Coder CLI installed and authenticated (
coder login <url>)
Setting up a new server? See the Server Setup Guide for step-by-step installation of Docker, Sysbox, and Coder.
-
Sysbox runtime installed on all Coder agent nodes:
# Install prerequisites sudo apt-get install -y jq # Download Sysbox CE package (check https://github.com/nestybox/sysbox/releases for latest version) SYSBOX_VERSION=0.6.7 wget https://downloads.nestybox.com/sysbox/releases/v${SYSBOX_VERSION}/sysbox-ce_${SYSBOX_VERSION}-0.linux_amd64.deb # Install the package (note: sysbox-ce is not in standard apt repos) sudo apt-get install -y ./sysbox-ce_${SYSBOX_VERSION}-0.linux_amd64.deb # Verify installation sudo systemctl status sysbox -n20 sysbox-runc --version
Note: Docker must be installed (not via snap) before installing Sysbox. The installer will restart Docker. See Sysbox install docs for details.
-
Docker configured to use Sysbox runtime for appropriate containers
-
Sufficient storage for Docker volumes (each workspace uses dedicated
/var/lib/dockervolume)
- Access to push images (Docker Hub or private registry)
- Registry credentials configured if using private registry
- For Docker Hub:
docker login
- Docker installed locally for building images
- Coder CLI installed and configured
- Git for version management
- Make (
sudo apt-get install -y makeon Ubuntu, pre-installed on macOS)
The base image contains Ubuntu, Docker daemon, DDEV, Node.js, and essential development tools.
The Makefile automates all build and deployment tasks:
# Show available commands
make help
# Build image with cache
make build
# Build without cache (clean build)
make build-no-cache
# Push to registry
make push
# Build and push in one step
make build-and-push
# Test the built image
make test
# Show version info
make infoSee image/README.md for details on customizing the Docker image.
The repository has a manually triggered workflow (.github/workflows/push-image.yml) that builds and pushes the image to Docker Hub from GitHub's infrastructure. This is the preferred approach for official releases.
Prerequisites — configure once in GitHub repository settings:
- Secret
PUSH_SERVICE_ACCOUNT_TOKEN— 1Password service account token (from thepush-secretsvault) - Variable
DOCKERHUB_USERNAME— Docker Hub username (e.g.ddev)
The workflow reads DOCKERHUB_TOKEN from 1Password at op://push-secrets/DOCKERHUB_TOKEN/credential using the service account token.
To trigger a push:
- Update
VERSIONand commit/merge to the branch you want to build from. - Go to Actions → Push Image → Run workflow in the GitHub UI, select the branch, and click Run workflow.
- The workflow builds
linux/amd64, tags the image as bothddev/coder-ddev:<version>andddev/coder-ddev:latest, and pushes to Docker Hub.
Alternatively, trigger via the CLI:
gh workflow run push-image.yml --ref <branch># Push all four templates (no image build — use when only HCL changed)
make push-all-templates
# Push a single template
make push-template-user-defined-web
make push-template-drupal-core
make push-template-drupal-contrib
make push-template-freeform
# Full deployment: build image, push image, push all templates
make deploy-all
# Full deployment without cache (clean image build)
make build-and-push-no-cache && make push-all-templatesSet a default auto-stop so idle workspaces shut down and free resources. Run once after initial deployment (or after adding a new template):
coder templates edit drupal-core --default-ttl 2h --yes
coder templates edit drupal-contrib --default-ttl 2h --yes
coder templates edit user-defined-web --default-ttl 2h --yes
coder templates edit freeform --default-ttl 2h --yesUsers can override the TTL on their individual workspaces if needed.
The template is defined in user-defined-web/template.tf. Key configuration parameters:
variable "workspace_image_registry" {
default = "index.docker.io/ddev/coder-ddev"
}
variable "image_version" {
default = "v0.1" # Update this when releasing new image versions
}
variable "cpu" {
default = 4 # CPU cores per workspace
}
variable "memory" {
default = 8 # RAM in GB per workspace
}
variable "docker_gid" {
default = 988 # Docker group ID (must match host)
}To use a private registry:
- Update
workspace_image_registryintemplate.tf - Configure
registry_usernameandregistry_passwordvariables - Push template:
coder templates push --directory user-defined-web user-defined-web --yes
The VERSION file in the root directory controls the image tag. The Makefile automatically copies it into the template directory before pushing, and template.tf reads it from there — no manual edits to template.tf are needed.
# 1. Update VERSION file
echo "v0.7" > VERSION
# 2. Build, push image, and push template (VERSION is synced automatically)
make deploy-user-defined-web
# Or without cache for clean build
make deploy-user-defined-web-no-cacheVia Web UI:
- Log into Coder dashboard
- Click "Create Workspace"
- Select "user-defined-web" template
- Enter workspace name
- Configure parameters (optional: CPU, memory)
- Click "Create Workspace"
Via CLI:
# Create with defaults
coder create --template user-defined-web my-workspace --yes
# Create with custom parameters
coder create --template user-defined-web my-workspace \
--parameter cpu=8 \
--parameter memory=16 \
--yes# List all workspaces
coder list
# List workspaces for specific template
coder list --template user-defined-web
# Show detailed workspace info
coder show my-workspace# Stop workspace (saves state, stops billing)
coder stop my-workspace
# Start workspace
coder start my-workspace
# Restart workspace
coder restart my-workspaceWhen you push a new template version, existing workspaces don't automatically update.
To update a workspace to new template version:
# Update in place (preserves /home/coder)
coder update my-workspace
# Or from web UI: Click workspace → Update buttonNotes:
- Updates template configuration but NOT the Docker image
- To update Docker image, workspace must be rebuilt (delete and recreate)
- Updating preserves
/home/codervolume - DDEV containers may need to be restarted after update
# Delete workspace (warns if running)
coder delete my-workspace
# Force delete
coder delete my-workspace --yes
# Delete multiple workspaces
for ws in workspace1 workspace2 workspace3; do coder delete "$ws" --yes; doneDeleting a workspace removes:
- Workspace container
/var/lib/dockerDocker named volume (Docker daemon data for that workspace)- Host directory at
/coder-workspaces/<owner>-<workspace>— via destroy-time provisioner in the template
If the provisioner doesn't run (e.g. Terraform error, or directories orphaned before this feature was added), use scripts/cleanup-deleted-workspaces.sh. See Orphaned Workspace Cleanup below.
# 1. Edit template.tf
vim user-defined-web/template.tf
# 2. Push updated template
make push-template-user-defined-web# 1. Edit image/Dockerfile (if needed)
# 2. Increment version (template reads this automatically)
echo "v0.7" > VERSION
# 3. Build and deploy
make deploy-user-defined-web
# Users must rebuild workspaces to get new Docker imagePush a new version without making it the default, test it on a single workspace, then promote it when satisfied. This lets you validate changes without affecting other users.
# 1. Push without activating
make push-template-drupal-core ACTIVATE=false
# 2. Find the new version name (top row, status "Unused")
coder templates versions list drupal-core
# 3. Create a test workspace pinned to that version
coder create --template drupal-core --template-version <version-name> test-workspace --yes
# 4. Verify — e.g. for drupal-core, check setup completed correctly
coder ssh test-workspace -- grep -E "Drush|Drupal install" ~/drupal-core/drupal-setup.log
# 5. Promote to active once satisfied
coder templates versions promote drupal-core <version-name>
# 6. Clean up test workspace
coder delete test-workspace --yesIf you push again with ACTIVATE=true (the default) rather than using promote, that also activates the version — the promote step is only needed when you pushed with ACTIVATE=false and want to activate without re-pushing.
# Revert to previous template version
git checkout <previous-commit> user-defined-web/template.tf
coder templates push --directory user-defined-web user-defined-web --yes
# Users on old version are unaffected
# Users can update to rollback version via: coder update <workspace>Each workspace stores persistent data in:
- Home directory:
/coder-workspaces/<owner>-<workspace>on host - Docker volume: Named volume
coder-<owner>-<workspace>-dind-cache
Backup strategy:
# Backup home directory
tar -czf workspace-backup.tar.gz /coder-workspaces/<owner>-<workspace>
# Backup Docker volume
docker run --rm \
-v coder-<owner>-<workspace>-dind-cache:/source \
-v $(pwd):/backup \
ubuntu:24.04 \
tar -czf /backup/docker-volume-backup.tar.gz -C /source .
# Restore Docker volume
docker volume create coder-<owner>-<workspace>-dind-cache
docker run --rm \
-v coder-<owner>-<workspace>-dind-cache:/target \
-v $(pwd):/backup \
ubuntu:24.04 \
tar -xzf /backup/docker-volume-backup.tar.gz -C /targetWhen disk space is running low, reclaim it in this order:
coder list -aStopped or dormant workspaces that are no longer needed can be deleted. This removes the workspace container, Docker volume, and host directory:
# Delete a single workspace
coder delete <owner>/<workspace-name> --yes
# Delete multiple workspaces
for ws in <owner>/<workspace1> <owner>/<workspace2>; do coder delete "$ws" --yes; done
# Delete all workspaces (use with caution)
for ws in $(coder list -a -c workspace | grep -v WORKSPACE); do coder delete "$ws" --yes; doneCheck the Coder dashboard for "Last used" times to identify dormant workspaces before deleting.
BuildKit caches can accumulate across workspace image builds:
docker buildx prune -fdf -h /data /coder-workspaces
docker system dfWhen a workspace is deleted, the destroy provisioner automatically removes the host directory at /coder-workspaces/<owner>-<workspace>. Directories can still be orphaned if the provisioner fails or for workspaces deleted before the provisioner was added. Run the cleanup script to reclaim disk space in those cases.
# Dry run — shows what would be deleted without removing anything
./scripts/cleanup-deleted-workspaces.sh
# Actually delete orphaned directories and Docker volumes
./scripts/cleanup-deleted-workspaces.sh --forceRun as your normal user (not root) — the script calls sudo /usr/local/bin/coder-delete-workspace-dir internally for directory removal, and uses docker volume rm (requires docker group membership).
The script:
- Queries
coder list --allto get the current list of active workspaces - Compares against directories in
/coder-workspaces/and Docker volumes namedcoder-*-dind-cache - Reports sizes before deleting
- Refuses to delete anything if the Coder CLI returns no workspaces (guards against misconfigured auth)
Prerequisites: coder CLI authenticated as an admin; user in the docker group; sudo access for directory removal.
Store template versions in git:
# Tag releases
git tag -a v0.1 -m "Release v0.1"
git push origin v0.1
# Track changes
git log --oneline user-defined-web/template.tfCheck workspace health:
# View workspace logs
coder ssh my-workspace -- journalctl -u coder-agent -f
# Check Docker daemon inside workspace
coder ssh my-workspace -- docker ps
coder ssh my-workspace -- docker info
# Check DDEV status
coder ssh my-workspace -- ddev listResource usage:
# Check workspace container resources
docker stats coder-<workspace-id>
# Check disk usage
df -h /coder-workspaces/
docker system dfSee troubleshooting.md for detailed debugging procedures.
Quick checks:
# Template deployment failed
coder templates list # Check if template exists
terraform validate # Validate template syntax
# Workspace won't start
coder logs my-workspace # View startup logs
docker logs coder-<workspace-id> # View container logs
# Docker daemon issues
coder ssh my-workspace -- cat /tmp/dockerd.log
coder ssh my-workspace -- systemctl status dockerSysbox provides secure nested containers without privileged mode:
- No
--privilegedflag required - Isolated Docker daemon per workspace
- AppArmor and seccomp profiles configured
Security profiles in template.tf:
security_opt = ["apparmor:unconfined", "seccomp:unconfined"]These are required for Sysbox functionality and are safer than --privileged.
- Each workspace has isolated Docker daemon
- Workspaces cannot access other workspaces' Docker containers
- Users have sudo access inside their workspace only
- Store registry credentials in Coder secrets or environment variables
- Avoid hardcoding credentials in template.tf
- Use private registries for proprietary images
- DDEV uses Coder's port forwarding (not direct host binding)
- Ports are proxied through Coder server
- Configure firewall rules on Coder server, not individual workspaces
- Default: 4 CPU cores, 8GB RAM (suitable for most PHP/Node projects)
- Large projects: 8 cores, 16GB RAM
- Small projects: 2 cores, 4GB RAM
Monitor resource usage and adjust template defaults accordingly.
- Use clear, descriptive names:
user-defined-web,ddev-developer - Version templates for major changes:
user-defined-web-v2 - Avoid generic names:
template1,test
- Always tag images with specific versions and
latest - Test images before pushing to production registry
- Document changes in git commit messages
- Keep Dockerfile layer count reasonable (current: ~10 layers)
- Short-lived workspaces: Ideal for temporary work, prototyping
- Long-lived workspaces: Personal development environments
- Stop workspaces when not in use to save resources
- Keep
CLAUDE.mdupdated for AI-assisted development - Update user docs in
/docs/user/when template changes - Document breaking changes in git tags and release notes
- Server Setup Guide - Fresh server installation (Docker, Sysbox, Coder)
- User Management Guide - Managing users and permissions
- Troubleshooting Guide - Debugging workspace issues
- Coder Documentation - Official Coder docs
- Sysbox Documentation - Sysbox runtime details
- DDEV Documentation - DDEV usage and configuration