Deploy and manage Docker Compose stacks from the command line. Supports two deployment modes:
- Portainer mode — Sync local Docker Compose files and environment variables to a Portainer instance via its API
- SSH mode — Push stacks directly to remote hosts via SSH and
docker compose, targeting dockge-style setups or any host with Docker Compose installed
- A Portainer instance with API access enabled
- A
PORTAINER_API_KEY(create one in Portainer under User Settings > Access Tokens)
- SSH access to the remote host (via key or agent)
docker composeinstalled on the remote host
brew install kyeotic/tap/stack-sync
curl -fsSL https://raw.githubusercontent.com/kyeotic/stack-sync/main/install | bashnix profile install github:kyeotic/stack-syncOr download a binary from the releases page.
- Create a
.stack-sync.tomlconfig file in your project directory:
host = "https://portainer.example.com"
endpoint_id = 2 # optional, default 2
portainer_api_key = "Your_Key"
[stacks.my-stack]
compose_file = "compose.yaml"
env_file = ".env" # optional
[stacks.other-stack]
compose_file = "other/compose.yaml"
env_file = "other/.env"
endpoint_id = 3 # optional per-stack override| Field | Description | Required |
|---|---|---|
host |
Portainer instance URL | Yes |
endpoint_id |
Default Portainer environment/endpoint ID | No |
stacks.<name> |
Stack definition — the key is the stack name | Yes |
compose_file |
Path to the local Docker Compose file | Yes |
env_file |
Path to the local .env file for stack variables |
No |
endpoint_id (per-stack) |
Override the top-level endpoint ID | No |
- Create a
.stack-sync.tomlconfig file:
mode = "ssh"
host = "192.168.0.20"
ssh_user = "root" # optional, defaults to current user
ssh_key = "~/.ssh/id_ed25519" # optional, uses ssh-agent if omitted
host_dir = "/mnt/app_config/docker"
[stacks.my-stack]
compose_file = "compose.yaml"
env_file = ".env" # optional| Field | Description | Required |
|---|---|---|
mode |
Set to "ssh" to enable SSH mode |
Yes |
host |
SSH hostname or IP address | Yes |
host_dir |
Remote directory where stacks are stored | Yes |
ssh_user |
SSH username | No |
ssh_key |
Path to SSH private key (~ is expanded) |
No |
Stacks are deployed to {host_dir}/{stack_name}/compose.yaml on the remote host, with an optional .env file alongside it. This layout is compatible with dockge and similar tools.
SSH mode shells out to the ssh command on your system, so it inherits your SSH agent, ~/.ssh/config, and known_hosts automatically.
- Deploy the stack:
stack-sync syncThis creates or updates all stacks defined in the config. To target specific stacks:
stack-sync sync my-stack
stack-sync sync my-stack other-stackSince creating a config file in your repo with secrets like the portainer_api_key is a bad practice, and since you're also likely to share the host with several projects, stack-sync supports config inheritance.
Global fields (host, portainer_api_key, endpoint_id, mode, ssh_user, ssh_key, host_dir) can be provided by a .stack-sync.toml in any parent directory up to and including your $HOME directory. This allows connection settings to be stored outside the working directory.
Alternatively you can provide a PORTAINER_API_KEY as an ENV VAR (e.g. sourced from a .env file by dir-env) and not put it any config files.
The order of precedence is
$PORTAINER_API_KEYENV VAR- current config (or config provided by
--config) - The next parent directory
Configs can also be merged: if the parent directory config contains an endpoint_id and the $HOME directory config contains a host they will form a complete configuration.
Create or update stacks using the local compose files and env vars.
stack-sync sync # sync all stacks
stack-sync sync my-stack # sync one stack
stack-sync sync my-stack other-stack # sync specific stacks
stack-sync sync my-stack --dry-run # preview changes
stack-sync sync -C /path/to/config.toml # use a different config fileThe config path defaults to the current directory, where it will automatically look for .stack-sync.toml first, then stack-sync.toml. File paths in the config (compose_file, env_file) are resolved relative to the config file's directory, not the working directory.
Show the current state of stacks on the remote.
stack-sync view # view all stacks
stack-sync view my-stack # view one stack
stack-sync view -C /path/to/config.toml # use a different config fileInitialize config files for a new project. Creates a parent config with credentials/connection settings and a local config with an example stack.
Portainer mode (default):
stack-sync init \
--portainer-api-key ptr_xxx \
--host https://portainer.example.com \
--endpoint-id 2 \
--parent-dir ~SSH mode:
stack-sync init \
--mode ssh \
--host 192.168.0.20 \
--host-dir /mnt/app_config/docker \
--ssh-user root \
--ssh-key ~/.ssh/id_ed25519 \
--parent-dir ~| Argument | Description | Required |
|---|---|---|
--mode |
Deploy mode: portainer or ssh |
No (default: portainer) |
--portainer-api-key |
Portainer API key | Portainer mode only |
--host |
Portainer URL or SSH hostname | Yes |
--endpoint-id |
Default endpoint ID (defaults to 2) | No |
--host-dir |
Remote directory for stacks | SSH mode only |
--ssh-user |
SSH username | No |
--ssh-key |
Path to SSH private key | No |
--parent-dir |
Directory for credentials config (default ~) | No |
--force |
Overwrite existing files | No |
This creates two files:
{parent-dir}/.stack-sync.toml— credentials/connection settings./.stack-sync.toml— local config with example stack commented out
Import an existing stack from Portainer (or a remote SSH host) into your local config. Downloads the compose file and env vars, and adds the stack to your config.
stack-sync import my-stack # import a stack
stack-sync import my-stack --force # overwrite existing files
stack-sync import my-stack -C /path/to/dir # use a different config directory| Argument | Description | Required |
|---|---|---|
<stack> |
Name of the stack to import | Yes |
-C |
Path to config file or directory | No |
--force |
Overwrite existing files | No |
Creates {stack}.compose.yaml and {stack}.env files, and adds a [stacks.{stack}] entry to the local config. In SSH mode, the stack is read from {host_dir}/{stack}/compose.yaml on the remote host.
Force a stack to re-pull images and redeploy. Useful after pushing new images to a registry. In Portainer mode, this uses the current configuration from Portainer (not local files). In SSH mode, it runs docker compose pull && docker compose up -d --force-recreate on the remote host.
stack-sync redeploy my-stack # redeploy a stack
stack-sync redeploy my-stack --dry-run # preview what would happen
stack-sync redeploy my-stack -C /path/to/dir # use a different config directory| Argument | Description | Required |
|---|---|---|
<stack> |
Name of the stack to redeploy | Yes |
-C |
Path to config file or directory | No |
--dry-run |
Preview without making changes | No |
The stack must exist in both the local config and on the remote (Portainer or SSH host).
Check for the latest release and update the binary in-place.
stack-sync upgradeNote: Self-update is not supported when installed via Nix. Use
nix profile upgrade --flake github:kyeotic/stack-syncinstead.
The .env file uses standard KEY=value format:
DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=sk-abc123
DEBUG=true
Lines starting with # and blank lines are ignored.