* feat(docker): make container user configurable via CF_UID/CF_GID Add support for running compose-farm containers as a non-root user to preserve file ownership on mounted volumes. This prevents files like compose-farm-state.yaml and web UI config edits from being owned by root on NFS mounts. Set CF_UID, CF_GID, and CF_HOME environment variables to run as your user. Defaults to root (0:0) for backwards compatibility. * docs: document non-root user configuration for Docker - Add CF_UID/CF_GID/CF_HOME documentation to README and getting-started - Add XDG config volume mount for backup/log persistence across restarts - Update SSH volume examples to use CF_HOME variable * fix(docker): allow non-root user access and add USER env for SSH - Add `chmod 755 /root` to Dockerfile so non-root users can access the installed tool at /root/.local/share/uv/tools/compose-farm - Add USER environment variable to docker-compose.yml for SSH to work when running as non-root (UID not in /etc/passwd) - Update docs to include CF_USER in the setup instructions - Support building from local source with SETUPTOOLS_SCM_PRETEND_VERSION * fix(docker): revert local build changes, keep only chmod 755 /root Remove the local source build logic that was added during testing. The only required change is `chmod 755 /root` to allow non-root users to access the installed tool. * docs: add .envrc.example for direnv users * docs: mention direnv option in README and getting-started
6.6 KiB
icon
| icon |
|---|
| lucide/rocket |
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)
curl -fsSL https://compose-farm.nijho.lt/install | sh
This installs uv if needed, then installs compose-farm.
Using uv
If you already have uv installed:
uv tool install compose-farm
Using pip
If you already have Python 3.11+ installed:
pip install compose-farm
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:
cp .envrc.example .envrc && direnv allow
This ensures files like compose-farm-state.yaml and web UI edits are owned by your user instead of root. The CF_USER variable is required for SSH to work when running as a non-root user.
Verify Installation
cf --version
cf --help
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"
Option 2: Dedicated Key (recommended for Docker)
For persistent access when running in Docker:
# Generate and distribute key to all hosts
cf ssh setup
# Check status
cf ssh status
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:
cd /opt/stacks
cf config init
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
cf check --local
This validates syntax without SSH connections. For full validation:
cf check
First Commands
Check Status
cf ps
Shows all configured stacks and their status.
Start All Stacks
cf up --all
Starts all stacks on their assigned hosts.
Start Specific Stacks
cf up plex grafana
Apply Configuration
The most powerful command - reconciles reality with your config:
cf apply --dry-run # Preview changes
cf apply # Execute changes
This will:
- Start stacks in config but not running
- Migrate stacks on wrong host
- Stop stacks removed from config
Docker Network Setup
If your stacks use an external Docker network:
# Create network on all hosts
cf init-network
# Or specific hosts
cf init-network nuc hp
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:
stacks:
# ... existing stacks
gitea: nuc
Start the stack:
cf up gitea
2. Move a Stack to Another Host
Edit compose-farm.yaml:
stacks:
plex: hp # Changed from nuc
Apply the change:
cf up plex
# Automatically: down on nuc, up on hp
Or use apply to reconcile everything:
cf apply
3. Update All Stacks
cf update --all
# Runs: pull + build + down + up for each stack
Next Steps
- Configuration Reference - All config options
- Commands Reference - Full CLI documentation
- Traefik Integration - Multi-host routing
- Best Practices - Tips and limitations