Getting Started¶
This guide walks you through installing Compose Farm and setting up your first multi-host deployment.
Prerequisites¶
Before you begin, ensure you have:
- uv (recommended) or Python 3.11+
- SSH key-based authentication to your Docker hosts
- Docker and Docker Compose installed on all target hosts
- Shared storage for compose files (NFS, Syncthing, etc.)
Installation¶
One-liner (recommended)¶
This installs uv if needed, then installs compose-farm.
Using uv¶
If you already have uv installed:
Using pip¶
If you already have Python 3.11+ installed:
Using Docker¶
docker run --rm \
-v $SSH_AUTH_SOCK:/ssh-agent -e SSH_AUTH_SOCK=/ssh-agent \
-v ./compose-farm.yaml:/root/.config/compose-farm/compose-farm.yaml:ro \
ghcr.io/basnijholt/compose-farm up --all
Running as non-root user (recommended for NFS mounts):
By default, containers run as root. To preserve file ownership on mounted volumes, set these environment variables in your .env file:
# Add to .env file (one-time setup)
echo "CF_UID=$(id -u)" >> .env
echo "CF_GID=$(id -g)" >> .env
echo "CF_HOME=$HOME" >> .env
echo "CF_USER=$USER" >> .env
Or use direnv to auto-set these variables when entering the directory:
This ensures files like compose-farm-state.yaml and web UI edits are owned by
your user instead of root. CF_USER also becomes the default SSH username for
hosts that do not set user explicitly.
Verify Installation¶
SSH Setup¶
Compose Farm uses SSH to run commands on remote hosts. You need passwordless SSH access.
Option 1: SSH Agent (default)¶
If you already have SSH keys loaded in your agent:
# Verify keys are loaded
ssh-add -l
# Test connection
ssh user@192.168.1.10 "docker --version"
# Verify and persist the configured servers' host keys for Compose Farm
cf ssh setup --trust-only
Option 2: Dedicated Key (recommended for Docker)¶
For persistent access when running in Docker:
This creates ~/.ssh/compose-farm/id_ed25519 and copies the public key to each host.
Shared Storage Setup¶
Compose files must be accessible at the same path on all hosts. Common approaches:
NFS Mount¶
# On each Docker host
sudo mount nas:/volume1/compose /opt/compose
# Or add to /etc/fstab
nas:/volume1/compose /opt/compose nfs defaults 0 0
Directory Structure¶
/opt/compose/ # compose_dir in config
├── plex/
│ └── docker-compose.yml
├── grafana/
│ └── docker-compose.yml
├── nextcloud/
│ └── docker-compose.yml
└── jellyfin/
└── docker-compose.yml
Configuration¶
Create Config File¶
Create compose-farm.yaml in the directory where you'll run commands. For example, if your stacks are in /opt/stacks, place the config there too:
Alternatively, use ~/.config/compose-farm/compose-farm.yaml for a global config. You can also symlink a working directory config to the global location:
# Create config in your stacks directory, symlink to ~/.config
cf config symlink /opt/stacks/compose-farm.yaml
This way, cf commands work from anywhere while the config lives with your stacks.
Single host example¶
# Where compose files are located (one folder per stack)
compose_dir: /opt/stacks
hosts:
local: localhost
stacks:
plex: local
grafana: local
nextcloud: local
Multi-host example¶
# Where compose files are located (same path on all hosts)
compose_dir: /opt/compose
# Define your Docker hosts
hosts:
nuc:
address: 192.168.1.10
user: docker # SSH user
hp:
address: 192.168.1.11
# user defaults to current user
# Map stacks to hosts
stacks:
plex: nuc
grafana: nuc
nextcloud: hp
Each entry in stacks: maps to a folder under compose_dir that contains a compose file.
For cross-host HTTP routing, add Traefik labels and configure traefik_file (see Traefik Integration).
Validate Configuration¶
This validates syntax without SSH connections. For full validation:
First Commands¶
Check Status¶
Shows all configured stacks and their status.
Start All Stacks¶
Starts all stacks on their assigned hosts.
Start Specific Stacks¶
Apply Configuration¶
The most powerful command - reconciles reality with your config:
This will: 1. Start stacks in config but not running 2. Migrate stacks on wrong host 3. Stop stacks removed from config
Docker Network Setup¶
If your stacks use an external Docker network:
Default network: mynetwork with subnet 172.20.0.0/16
Example Workflow¶
1. Add a New Stack¶
Create the compose file:
# On any host (shared storage)
mkdir -p /opt/compose/gitea
cat > /opt/compose/gitea/docker-compose.yml << 'EOF'
services:
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- /opt/config/gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
restart: unless-stopped
EOF
Add to config:
Start the stack:
2. Move a Stack to Another Host¶
Edit compose-farm.yaml:
Apply the change:
Or use apply to reconcile everything:
3. Update All Stacks¶
Next Steps¶
- Configuration Reference - All config options
- Commands Reference - Full CLI documentation
- Traefik Integration - Multi-host routing
- Best Practices - Tips and limitations