Agentless remote deployment tool for Docker Compose stacks.
ssd is a lightweight CLI tool that simplifies deploying Docker applications to remote servers via SSH. No agents, no complex setup—just SSH access and Docker Compose.
- Simple: Convention-over-configuration approach
- Flexible: Works with monorepos and simple projects
- Agentless: Only requires SSH access and Docker on the server
- Smart: Auto-increments build numbers
- Fast: Builds on the server, no image registry needed
- Reliable: Force-recreate deployment with automatic version tracking
Quick install (Linux/macOS)
curl -sSL https://raw.githubusercontent.com/byteink/ssd/main/install.sh | shHomebrew (macOS/Linux)
brew install byteink/tap/ssdGo
go install github.com/byteink/ssd@latestLinux packages
Download from Releases:
- Debian/Ubuntu:
ssd_*_linux_amd64.deb - RHEL/Fedora:
ssd_*_linux_amd64.rpm
Windows
Download ssd_Windows_x86_64.zip from Releases, extract, and add to PATH.
- Initialize your project:
# Interactive mode
ssd init
# Or with flags
ssd init -s myserver -d myapp.example.com -p 3000- Deploy:
ssd deploy appThat's it! ssd will:
- Sync your code to the server via rsync
- Build the Docker image on the server
- Auto-increment the version number
- Update compose.yaml and restart the stack
# ssd.yaml
server: myserver
services:
app:
# name defaults to service key ("app")
# stack defaults to /stacks/app# ssd.yaml
server: myserver
stack: /custom/stacks/myapp # Shared by all services
services:
web:
name: myapp-web
context: ./apps/web
dockerfile: ./apps/web/Dockerfile# ssd.yaml
server: myserver
stack: /stacks/myproject # All services share this stack
services:
web:
context: ./apps/web
dockerfile: ./apps/web/Dockerfile
api:
context: ./apps/api
dockerfile: ./apps/api/DockerfileDeploy specific service:
ssd deploy web# ssd.yaml
server: myserver
services:
web:
name: myapp-web
stack: /stacks/myapp
context: ./apps/web
dockerfile: ./apps/web/Dockerfile
target: production # Docker build target stage (optional)
domain: example.com # Enable Traefik routing
path: /api # Path prefix routing (optional)
https: true # Default true, set false to disable
port: 3000 # Container port, default 80
ports: # Host:container port mappings (optional)
- "3000:3000"
- "8080:80"
depends_on: # Simple list or map with conditions
- db
- redis
volumes:
postgres-data: /var/lib/postgresql/data
redis-data: /data
healthcheck:
cmd: "curl -f http://localhost:3000/health || exit 1"
interval: 30s
timeout: 10s
retries: 3# ssd.yaml
server: myserver
services:
web:
depends_on:
db:
condition: service_healthy
redis:
condition: service_startedConditions: service_started (default), service_healthy (requires healthcheck), service_completed_successfully.
# ssd.yaml
server: myserver
services:
app:
ports:
- "3000:3000" # Expose on host for Tailscale/CF tunnelWhen no domain or domains is set, the service is deployed without Traefik labels or the traefik_web network. Use ports to map host:container ports for access via Tailscale, Cloudflare tunnels, or direct host access.
# ssd.yaml
server: myserver
services:
nginx:
image: nginx:latest # Use pre-built image, skip build step
domain: example.com# ssd.yaml
server: myserver
services:
web:
domains:
- example.com
- www.example.com
- api.example.com
port: 3000All domains work independently, no redirects. Useful for multi-brand apps, different locales, or A/B testing.
# ssd.yaml
server: myserver
services:
web:
domains:
- example.com
- www.example.com
- old-domain.com
redirect_to: example.com # All other domains redirect to this
port: 3000When redirect_to is set, all other domains automatically redirect to it with a 302 temporary redirect. Common use cases:
- www redirect: Redirect www to non-www (or vice versa)
- Domain migration: Redirect old domains to new primary domain
- Multi-TLD consolidation: Redirect .net, .org to primary .com
# ssd.yaml
server: myserver
stack: /stacks/myapp
services:
api:
context: ./apps/api
dockerfile: ./apps/api/Dockerfile
domain: api.example.com
port: 8080
depends_on:
- db
healthcheck:
cmd: "curl -f http://localhost:8080/health || exit 1"
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16-alpine
volumes:
postgres-data: /var/lib/postgresql/data
healthcheck:
cmd: "pg_isready -U postgres"
interval: 10s
timeout: 5s
retries: 5Service-level fields:
name: Service name (defaults to service key)stack: Path to stack directory on server (defaults to/stacks/{name})context: Build context path (defaults to.)dockerfile: Dockerfile path (defaults to./Dockerfile)image: Pre-built image to use (skips build step if specified)target: Docker build target stage for multi-stage builds (e.g.,production)domain: Single domain for Traefik routingdomains: Multiple domains for Traefik routing. Cannot use bothdomainanddomainsredirect_to: When set, all domains except this one redirect to it (302 temporary). Must be one of the domains indomainsarraypath: Path prefix for routing (e.g.,/api). Requiresdomainordomains. GeneratesPathPrefixrule withStripPrefixmiddlewarehttps: Enable HTTPS (default:true)port: Container port (default:80)ports: Host:container port mappings (e.g.,["3000:3000"]). Maps directly to Docker Composeports:depends_on: Service dependencies (list or map with conditions)volumes: Map of volume names to mount pathshealthcheck: Health check configurationcmd: Health check commandinterval: Check interval (e.g.,30s)timeout: Command timeout (e.g.,10s)retries: Number of retries before unhealthy
Root-level fields:
server: SSH server name (from~/.ssh/config)stack: Default stack path for all services
ssd init # Interactive mode
ssd init -s myserver # Non-interactive with flagsFlags:
-s, --server- SSH host name (required in non-interactive mode)--stack- Stack path (e.g.,/dockge/stacks/myapp)--service- Service name (default:app)-d, --domain- Domain for Traefik routing--path- Path prefix for routing (e.g.,/api)-p, --port- Container port-f, --force- Overwrite existingssd.yaml
ssd deploy [service] # Deploy service (or all if omitted)
ssd restart <service> # Restart without rebuilding
ssd rollback <service> # Rollback to previous version
ssd status <service> # Check container status
ssd logs <service> [-f] # View logs, -f to followDeploy behavior:
- With no argument, deploys all services in alphabetical order
- With a service name, deploys that single service
- Dependencies are started first (respects
depends_on) - Example:
ssd deploy apiwill also startdbifapidepends on it
ssd config # Show all services config
ssd config <service> # Show specific service configssd env <service> set KEY=VALUE # Set environment variable
ssd env <service> list # List all environment variables
ssd env <service> rm KEY # Remove environment variableNote: Environment variables are stored in .env file in the stack directory on the server.
ssd provision # Provision server with Docker and TraefikProvisions the target server with:
- Docker and Docker Compose installation
- Traefik reverse proxy with automatic HTTPS (Let's Encrypt)
- Docker network for service discovery
Note: This command is planned but not yet implemented.
ssd version # Show version
ssd help # Show help- Reads
ssd.yamlfrom current directory - SSHs into the configured server (uses
~/.ssh/config) - Rsyncs code to a temp directory (excludes .git, node_modules, .next)
- Builds Docker image on the server (or skips if using pre-built
image) - Parses current version from compose.yaml, increments it
- Recreates the service with
docker compose up -d --force-recreate - Cleans up temp directory
- SSH access to target server (configured in
~/.ssh/config) - Docker and Docker Compose on the server
- A
compose.yamlalready set up in the stack directory - rsync installed locally
# Clone and setup
git clone https://github.com/byteink/ssd.git
cd ssd
make setup # Configures git hooks for linting
# Build and test
make build # Build binary
make test # Run tests
make lint # Run linterThis repo includes a ready-made Claude Code skill file that lets Claude deploy and manage your services using ssd via the /ssd slash command.
Copy the skill file into your project's .claude/skills/ directory:
mkdir -p .claude/skills/ssd
cp /path/to/ssd/SKILL.md .claude/skills/ssd/SKILL.mdOr with curl:
mkdir -p .claude/skills/ssd
curl -sSL https://raw.githubusercontent.com/byteink/ssd/main/SKILL.md -o .claude/skills/ssd/SKILL.mdOnce installed, use the /ssd slash command in Claude Code:
/ssd deploy web
/ssd status api
/ssd logs web -f
/ssd rollback api
Or ask Claude naturally and it will use ssd when appropriate.
MIT
Built by ByteInk