Skip to content

Repository files navigation

slip

Zero-downtime container deploys from CI, without Kubernetes.

slip is a lightweight deployment daemon written in Rust. It runs on your server, accepts signed webhooks from CI, and manages zero-downtime container deployments using Caddy as the reverse proxy.

No SSH keys in CI. No PaaS overhead. No Kubernetes.

GitHub Actions → signed webhook → slipd → pull → health check → swap route → stop old

Features

  • Zero-downtime deploys — blue-green (start new, swap, drain old) or recreate (stop old, start new)
  • HMAC-SHA256 auth — CI gets one secret per app, no SSH keys on runners
  • Caddy integration — automatic HTTPS, dynamic route management via admin API
  • Pod support — multi-container pods via podman kube play (init containers, shared volumes, per-container env)
  • Worker apps — non-HTTP workloads (pipelines, daemons) with container-liveness health checks
  • Multiple routes — one app, multiple Caddy hostnames (e.g. api.example.com + dagster.example.com)
  • Persistent volumes — host-path bind mounts that survive redeploys
  • Multi-image deploys — one webhook, multiple container images (${tag} placeholders + per-container overrides)
  • Preview environments — ephemeral deploys with TTL, subdomain routing, wildcard TLS
  • SQLite deploy history — STRICT tables, survives restarts, queryable via API
  • Secrets management — per-app secret store with restrictive file permissions
  • Per-container health — mixed HTTP + worker containers in a single pod
  • Structured logging — JSON logs via tracing
  • slip init — scaffolds a repo's slip.toml, CI workflow, and agent contract
  • slip status — daemon overview (uptime, Caddy/runtime health, app table) and per-app detail (tag, container state, health probe, last deploy, routes, cert issuer, secret keys, config drift)
  • slip logs — remote container log tail/follow via the management API
  • slip doctor — host diagnostics (DNS, UFW, runtime, registry, Caddy, TLS) with prescriptive remedies
  • slip apply — the repo's slip.toml becomes the source of truth (validate → diff → push)

Install

curl -sSL https://raw.githubusercontent.com/mitchelljphayes/slip/main/install.sh | bash

On Linux x86_64/aarch64 this downloads a prebuilt static (musl) binary, creates a slip service user, sets up directories, and installs a systemd unit.

On all other hosts (macOS today, or when no prebuilt binary exists for the detected OS+arch) the installer builds from source via cargo. That path requires the Rust toolchain and git to be present — the script will fail with a prescriptive error naming the remedy if they're missing. Pass --version <tag> to pin a release, or run the script from inside a slip checkout to build the current working tree without pinning.

Manual install (build from source, the explicit alternative):

git clone https://github.com/mitchelljphayes/slip.git
cd slip
cargo build --release
sudo cp target/release/slipd target/release/slip /usr/local/bin/

See docs/getting-started.md for the full setup guide.

Quick start

1. Configure slipd

sudo tee /etc/slip/slip.toml > /dev/null << 'EOF'
[server]
listen = "127.0.0.1:7890"

[runtime]
backend = "auto"          # auto-detects Podman or Docker

[auth]
secret = "${SLIP_SECRET}"  # global HMAC fallback; per-app [app] secret overrides it

[registries.ghcr]
url = "ghcr.io"
# username = "slip"              # optional: anonymous pull if absent
# token = "${GHCR_TOKEN}"        # optional: prefer `slip registry login ghcr.io`

[caddy]
admin_api = "http://localhost:2019"
acme_email = "you@example.com"   # required for the acme / cloudflare-dns01 TLS strategies

[storage]
path = "/var/lib/slip"

[deploy]
domain = "deploy.example.com"    # slipd registers the webhook route + TLS policy itself
# tls = "acme"                   # default: publicly trusted, so CI can verify it.
                                 # Alternatives: cloudflare-dns01, tailscale, internal
EOF

The [auth] section is required. [registries.<name>] is optional (omit for anonymous pulls); [auth].secret is the fallback webhook secret; a per-app [app] secret in the app TOML overrides it. slip secrets set <app> SLIP_SECRET=... does NOT affect webhook auth — it injects the value as a container env var only. See getting-started.md §6 for the full distinction.

2. Create an app config

sudo tee /etc/slip/apps/myapp.toml > /dev/null << 'EOF'
[app]
name = "myapp"
image = "ghcr.io/you/myapp"

[routing]
domain = "myapp.example.com"
port = 8080

[health]
path = "/health"
interval = "2s"
timeout = "5s"
retries = 5
start_period = "10s"

[deploy]
strategy = "blue-green"
drain_timeout = "30s"

[resources]
memory = "512m"
cpus = "1.0"
EOF

Each app is a separate file in /etc/slip/apps/ with a single [app] table (not [[apps]]). The [health] section is required; omit path to skip the HTTP probe and health-check on "container running" instead.

3. Set the webhook secret

The webhook signing key is either the global [auth].secret from slip.toml or a per-app [app] secret in the app TOML. To set a per-app secret, add secret under [app] in the app config:

[app]
name = "myapp"
image = "ghcr.io/you/myapp"
secret = "your-hmac-key"   # overrides global [auth].secret for this app

# ... rest of the app config unchanged

slip secrets set <app> SLIP_SECRET=... does NOT affect webhook auth. That command injects the value as a container environment variable only. See getting-started.md §6 for the full distinction.

4. Start the daemon

sudo slipd --config /etc/slip --check    # validate config
sudo systemctl enable --now slipd       # start the service

5. Deploy from CI

PAYLOAD='{"app":"myapp","tag":"sha-abc123f"}'
SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SLIP_SECRET" | cut -d' ' -f2)

curl -X POST https://deploy.example.com/v1/deploy \
  -H "Content-Type: application/json" \
  -H "X-Slip-Signature: sha256=$SIG" \
  -d "$PAYLOAD"

Or use the CLI:

slip deploy myapp sha-abc123f \
  --server https://deploy.example.com \
  --secret "$SLIP_SECRET"

Comparison

Feature slip Dokku CapRover Kamal k8s
Signed webhooks (no SSH in CI)
Single binary, no runtime deps
Blue-green + recreate strategies ✅ (blue-green)
Pod support (kube play)
Worker / non-HTTP apps
Preview deployments
Persistent volumes
Multiple routes per deploy
Cost Free Free Free Free $73+/mo

App types

Single-container (blue-green)

[app]
name = "api"
image = "ghcr.io/you/api"

[routing]
domain = "api.example.com"
port = 8080

[health]
path = "/health"

[deploy]
strategy = "blue-green"

Worker (non-HTTP, e.g. a single-writer service)

The repo's slip.toml declares kind = "worker". No domain, no port, health = container running. Pair with strategy = "recreate" for single-writer state.

[app]
name = "pipeline"
image = "ghcr.io/you/pipeline"

[health]
# no path → health-check on "container running"

[deploy]
strategy = "recreate"

[[volumes]]
host_path = "/var/lib/slip/volumes/pipeline/state"
mount_path = "/app/data"
read_only = false

Pod (multi-container)

The repo declares kind = "pod" with a pod.yaml manifest. slip renders it and runs via podman kube play. Multiple routes target containers by name.

[app]
name = "statstream"
image = "ghcr.io/you/statstream-api"

[health]
path = "/health"

[deploy]
strategy = "recreate"

[[routing.routes]]
hostname = "statstream.example.com"
container = "api"

[[routing.routes]]
hostname = "dagster.example.com"
container = "dagster-webserver"

[[volumes]]
host_path = "/var/lib/slip/volumes/statstream/dagster-home"
mount_path = "/opt/dagster/dagster_home"

Project structure

Cargo workspace with three crates:

Crate Description
slipd Deploy daemon — receives webhooks, manages containers, talks to Caddy
slip CLI — app management, secrets, status, manual deploys
slip-core Shared library — config, types, Docker/Podman/Caddy clients

API

Endpoint Method Description
/v1/deploy POST Trigger a deploy (signed webhook)
/v1/status GET Daemon health + all app statuses
/v1/deploys/{id} GET Deploy progress and status
/v1/apps/{name} GET/PATCH/DELETE App config management
/v1/apps/{name}/rollback POST Rollback to previous deploy
/v1/secrets/{app}/{key} PUT/GET/DELETE Per-app secrets

Development

# Run tests
cargo test

# Check formatting + lints
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings

# Run smoke test (requires Docker + Caddy)
./scripts/smoke-test.sh

Documentation

Roadmap

  • Phase 1: Core Deploy Loop — webhook, deploy, health check, Caddy swap
  • Phase 2: Pods, Previews & CLI — Podman support, pod deploys, preview environments, CLI, volumes, workers, multi-image, multi-route
  • Phase 3: Production Hardening — deploy timeout, image pruning, multi-registry, systemd packaging
  • Phase 4: CI Integration — reusable GitHub Actions workflow, deploy status callbacks, slip deploy --wait

License

MIT

About

A container docking for your server. Lightweight deploy daemon with zero-downtime blue-green deployments via Caddy.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages