-
Notifications
You must be signed in to change notification settings - Fork 1
Deployment
Deploy and manage Docker stacks on VPS infrastructure. strut supports dry-run previews, service profiles, blue-green releases, and one-command rollback.
strut my-stack deploy --env prod --dry-run # Preview
strut my-stack deploy --env prod # DeployThere is one deploy command, and it goes wherever the stack lives. If the stack maps to a VPS, deploy runs the full pipeline on that host over SSH: sync the repo → run migrations → pull images → restart services → verify health → roll back if unhealthy. If the stack has no VPS, the same command deploys against your local Docker daemon.
You never pick the target by choosing a command name — the stack's topology already knows it. See Multi-Host Topology for how a stack resolves to a host.
| Situation | What deploy does |
|---|---|
| Stack maps to a VPS | Full pipeline on that VPS, over SSH |
| Stack has no VPS mapping | Local Docker daemon |
| You're already on the VPS | Local Docker daemon (no SSH round trip) |
--local passed |
Local Docker daemon, always |
--require-remote passed |
Fails rather than ever going local |
Related commands:
| Command | What It Does |
|---|---|
release |
Alias for deploy --require-remote — see below |
update |
Sync the repo on the VPS only, restart nothing |
stop |
Stop containers |
release used to be the separate verb for "deploy to the VPS", while deploy always meant "deploy right here". That split was the single most common source of mistakes with strut: the two read like alternatives, but release was actually the orchestrator and deploy was the primitive it SSHed in to run.
Now that deploy resolves its own target, release is exactly deploy --require-remote, and it remains as a permanently-supported alias. Existing scripts, runbooks, and CI jobs keep working unchanged — there is no deprecation warning and no removal planned. New docs and examples use deploy.
Use this anywhere a local deploy could never be what you meant — CI runners, automation, agent tooling:
strut my-stack deploy --env prod --require-remoteWithout it, a stack whose VPS_HOST fails to resolve falls back to the local Docker daemon and exits 0 — on a CI runner that's a successful-looking no-op that deployed nothing. With it, that case is a hard error. The GitHub Action and the MCP Server both pass it automatically.
Skip individual steps when you don't need them:
strut my-stack deploy --env prod --no-sync # Don't git-sync; deploy the code already on the host
strut my-stack deploy --env prod --no-migrate # Skip the migration steps
strut my-stack deploy --env prod --no-rollback # Don't auto-roll-back on health failure
strut my-stack deploy --env prod --backup-first # Back up databases before deploying
strut my-stack deploy --env prod --strict # Halt the deploy if a migration fails--no-sync is the one to reach for when you've changed an env var or image tag and want to restart the stack without shipping new code.
To deploy against your local Docker daemon even though the stack maps to a VPS:
strut my-stack deploy --env prod --local(--force-local is the older spelling and still works.)
If you want to drive the steps yourself:
strut my-stack update --env prod # Sync strut repo on VPS
strut my-stack deploy --env prod --no-sync # Deploy the code now on the host
strut my-stack health --env prod --json # Verifystrut <stack> diff shows semantic changes between local state and the VPS: which env vars would change and which compose image tags would move. Exit 1 when changes exist (useful as a CI gate), 0 when identical.
strut my-stack diff --env prod
strut my-stack diff --env prod --json
# Typical CI gate: block PR merge when prod has pending changes
strut my-stack diff --env prod || echo "pending changes"Since v0.13.0. Complements drift (VPS → local) and deploy --dry-run (command-level preview).
For zero-downtime releases, enable blue-green mode. strut stands the new version up alongside the old one under a <stack>-<env>-<color> compose project, health-gates it, swaps the reverse proxy, and drains the old color.
strut my-stack deploy --env prod --blue-green
strut my-stack deploy --env prod --standard # force in-place, overrides configDefault for a project: set DEPLOY_MODE=blue-green in strut.conf. Rollback uses a fast state-file flip instead of an image restore. See Blue-Green Deploy for proxy-hook examples, compose-file requirements, and failure modes.
After docker compose up -d, strut polls container health before printing the success banner. A crash-looping or unhealthy stack now fails the deploy instead of silently succeeding.
How it works: The deploy polls containers every 3 seconds, checking that all are running and passing Docker health checks (or have no healthcheck). Three consecutive healthy polls are required. On timeout or restart detection, the deploy fails with reason=health_gate_failed, fires the on_health_fail hook, and sends a deploy.failed notification.
Environment variables:
| Variable | Default | Description |
|---|---|---|
DEPLOY_HEALTH_TIMEOUT |
60 |
Seconds to wait for containers to become healthy |
DEPLOY_SKIP_HEALTH_GATE |
false |
Set to true or 1 to skip the gate entirely |
CLI flag:
strut my-stack deploy --env prod --skip-health-gate # skip for one-shot/migration stacksWhen to skip: Stacks with intentionally short-lived containers (one-shot migrations, seed scripts) that exit after up -d. These would fail the gate because no containers remain running.
Performance: Most healthy stacks pass in ~9s (3 polls × 3s), much faster than the old fixed 60s wait.
strut my-stack deploy --env prod # core (default)
strut my-stack deploy --env prod --services messaging # + messaging
strut my-stack deploy --env prod --services full # everythingProfiles map to Docker Compose profiles defined in your docker-compose.yml.
strut my-stack stop --env prod # Stop containers
strut my-stack stop --env prod --volumes # Stop + remove volumes
strut my-stack stop --env prod --dry-run # Preview what would stopAll destructive operations support --dry-run:
strut my-stack deploy --env prod --dry-run
strut my-stack stop --env prod --dry-run
strut my-stack backup postgres --env prod --dry-run
strut my-stack rollback --env prod --dry-run
strut my-stack domain example.com admin@example.com --env prod --dry-runThis shows the execution plan without making any changes.
- Provision the host (if needed):
strut harbor provision # Run setup script - Bootstrap strut on the VPS:
strut my-stack remote:init --env prod # Clone repo, install strut - Create env file from template:
strut my-stack init-secrets --env prod # Generate secrets nano .prod.env # Fill in remaining values
- Push secrets to VPS:
strut my-stack secrets push --env prod
- Deploy:
strut my-stack deploy --env prod
strut my-stack health --env prod --json # Health checks
strut my-stack status --env prod # Container status
strut my-stack logs my-service --since 30m --env prod # Recent logsDeploy automatically validates config before pulling images. If validation fails, the deploy aborts before making any changes.
# [2/7] Pre-deploy validation...
# ✓ strut.conf: valid
# ✓ services.conf: valid (3 services, 2 databases)
# ✓ docker-compose.yml: syntax valid
# ✓ secrets: no issues detected
# ✓ Pre-deploy validation passedChecks run automatically:
-
strut validate— config schema validation + secret scanning (since v0.9.0) -
docker compose config— compose file syntax - Custom lifecycle hooks (see Lifecycle Hooks)
The secret scanner flags known credential patterns (GitHub PATs, AWS access keys, sk-* API keys, Slack webhooks) and weak/placeholder values in PASSWORD/SECRET/TOKEN/KEY vars. It also errors if an env file is tracked by git.
Skip for emergencies:
strut my-stack deploy --env prod --skip-validationConfigure in strut.conf:
PRE_DEPLOY_VALIDATE=true # Run config validation (default: true)
PRE_DEPLOY_HOOKS=true # Run custom hooks (default: true)Since v0.13.0, deploy acquires a local lock under ~/.strut/locks/<stack>-<env>.lock.d/ (and a remote lock under <deploy_dir>/.strut-locks/ when VPS_HOST is set) so two concurrent deploys can't race.
strut my-stack lock status --env prod
strut my-stack lock release --env prod --force # break a stuck lock
strut my-stack deploy --env prod --force-unlock # break + deploy
strut my-stack deploy --env prod --no-lock # advanced: skip entirelyLocks auto-release on process exit via the unified EXIT trap. Locks older than STRUT_LOCK_STALE_SECONDS (default 600s) are detected as stale by lock status.
Each stack can drop executable scripts into stacks/<stack>/hooks/:
| Hook | Fires | Abort on non-zero? |
|---|---|---|
pre_deploy.sh |
before deploy | yes |
post_deploy.sh |
after deploy | no (warn) |
pre_backup.sh |
before backup | yes |
post_backup.sh |
after backup | no (warn) |
on_health_fail.sh |
after health check fails | no (warn) |
on_drift_detected.sh |
when drift is found | no (warn) |
Snake_case and dash forms both work (e.g. pre-deploy.sh). See Lifecycle Hooks for event payload env vars and examples.
If a deploy goes wrong, roll back to the previous container images:
strut my-stack rollback --env prod # Restore previous deploy
strut my-stack rollback --env prod --list # List available snapshots
strut my-stack rollback --env prod --dry-run # Preview rollbackSnapshots are saved automatically before each deploy. See Deploy Rollback for details.
Run database migrations as part of deployment:
strut my-stack migrate postgres --status --env prod # Check pending
strut my-stack migrate postgres --up --env prod # Apply
strut my-stack migrate neo4j --down 1 --env prod # Rollback one- Always
--dry-runfirst for destructive commands - Backup before major changes:
strut my-stack backup all --env prod - Use
--no-syncwhen you're changing config, not code — it skips the git sync and just restarts - Keep
services.confup to date — it drives health checks - Use
required_varsto catch missing env vars before deploy
strut now dry-runs git clean on the VPS checkout before executing the actual clean. If untracked, non-ignored paths are found, the operation aborts with a message listing the affected files and guidance on how to resolve them. This prevents accidental deletion of runtime data that lives inside the checkout.
Override with --force-clean when you're certain the untracked files are safe to remove.
Before deploying, strut compares volume-defining environment variables (any var ending in _PATH, _DATA_PATH, or DATA_VOLUME_*) between the local env file and the remote. If values differ, the deploy aborts with a warning that data may be orphaned or lost.
Override with --confirm-data-move when an intentional volume relocation is planned.
- Store persistent data outside the checkout directory, or add bind-mount paths to
.gitignore. - Review
strut <stack> diff --env prodbefore deploying to catch volume path changes early. - See Git Clean Safety for a deeper walkthrough of the guard behavior.
- Ship and Rebuild — Commit/push/rebuild in one step; image builds on remote
- Blue-Green Deploy — Zero-downtime deploys
- Deploy Rollback — Automatic snapshots and rollback
-
Secrets Management — Generate and sync
.envfiles - Provisioning — One-time host setup scripts
strut · v0.28.0 · Report an Issue
Getting Started
Core Concepts
Operations
- Deployment
- Ship and Rebuild
- GitHub Action
- Webhook Automation
- Remote Host Setup
- Provisioning
- Blue-Green Deploy
- Deploy Rollback
- Database Backups
- Secrets Management
- Stack Groups
- Lifecycle Hooks
- Notifications
- Key Rotation
- Drift Detection
- Domain and SSL
- Certificate Management
- Gateway Management
- Monitoring
- Volume Management
Advanced
- Security Posture
- VPS Audit and Migration
- Stack Validation
- Data Anonymization
- Debugging
- Local Development
Extending
Contributing