-
Notifications
You must be signed in to change notification settings - Fork 2
GitHub Action
Deploy a strut-managed Docker stack to a VPS from GitHub Actions in one step.
- uses: gfargo/strut-action@v1
with:
stack: my-app
command: deploy
env: prod
host: ${{ secrets.STRUT_HOST }}
ssh-key: ${{ secrets.STRUT_SSH_KEY }}- Your repository contains
strut.confand astacks/<stack>/directory (runstrut init+strut scaffold <stack>locally first). - The VPS already has strut bootstrapped (
strut remote:initor manual setup). - Two repository secrets are configured (Settings → Secrets and variables → Actions):
-
STRUT_HOST— VPS hostname or IP address -
STRUT_SSH_KEY— Private SSH key whose public half is in~/.ssh/authorized_keyson the VPS
-
The workflow must actions/checkout before the strut action so strut.conf and the stacks/ tree are present on the runner.
| Input | Required | Default | Description |
|---|---|---|---|
stack |
✓ | — | Stack name (subdirectory under stacks/) |
command |
deploy |
strut command — see Command semantics | |
env |
— | Environment name — resolves to .<env>.env in the project root. Empty uses .env
|
|
ssh-key |
✓ | — | Private SSH key contents (VPS_SSH_KEY) — always pass via a secret |
host |
— | VPS hostname or IP (VPS_HOST). Optional if your repo's topology already resolves the stack to a host |
|
user |
— | SSH user on the VPS (VPS_USER) |
|
port |
22 |
SSH port on the VPS (VPS_PORT) |
|
known-hosts |
— |
known_hosts entries for the VPS. If empty, the host key is fetched via ssh-keyscan
|
|
services |
— | Services profile passed as --services <profile>
|
|
strict |
false |
Pass --strict — treat migration failures as fatal |
|
dry-run |
false |
Pass --dry-run — print the plan without making changes |
|
args |
— | Extra raw arguments appended to the strut command | |
env-file |
— | Full contents of the .<env>.env file — always pass via a secret |
|
strut-version |
latest |
strut version to install: latest, main, or vX.Y.Z
|
|
working-directory |
. |
Project root containing strut.conf and stacks/
|
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gfargo/strut-action@v1
with:
stack: my-app
host: ${{ secrets.STRUT_HOST }}
ssh-key: ${{ secrets.STRUT_SSH_KEY }}name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
dry-run:
description: 'Preview without deploying'
default: 'false'
type: boolean
jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: deploy-prod
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: gfargo/strut-action@v1
with:
stack: my-app
command: deploy
env: prod
host: ${{ secrets.STRUT_HOST }}
ssh-key: ${{ secrets.STRUT_SSH_KEY }}
strut-version: v0.45.0 # pin to a specific strut release
strict: true # fail on migration errors
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
env-file: ${{ secrets.STRUT_PROD_ENV }}Note:
strut healthruns against the runner's local Docker daemon, not the VPS. It cannot verify stack health on a hosted runner. To check the VPS after deploying, use your own smoke-test step (e.g.curlthe health endpoint) or runstrut <stack> statusover SSH.
| Command | Runs where | Use case |
|---|---|---|
deploy |
Runner (SSH to VPS) |
Primary CI command. Runs sync → migrate → deploy → verify on the VPS the stack maps to. The action adds --require-remote, so a stack that resolves to no VPS fails loudly instead of deploying to the runner. |
release |
Runner (SSH to VPS) | Alias for deploy. Accepted so existing workflows keep working. |
ship |
Runner (git + SSH to VPS) | Like deploy but also commits and pushes from the runner. Needs contents: write permission and full checkout. See security warning below. Prefer deploy in most CI pipelines. |
health |
Runner's local Docker | Checks docker compose ps on the runner — not the VPS. Not useful on a hosted runner (Docker is empty). Omit from hosted CI; use a smoke-test curl or SSH step instead. |
Rule of thumb: use
deployfrom a hosted runner. Usehealthonly on a self-hosted runner running on the VPS itself.
ship runs git add -A on the runner before pushing. The action writes a .<env>.env file (containing VPS_HOST and your env-file contents) to the repository root. If your repo does not gitignore *.env, this file will be committed and pushed, leaking secrets.
Before using command: ship:
- Add
*.env(or.prod.env,.staging.env, etc.) to your repo's.gitignore. - Confirm the gitignore is committed before the workflow runs.
For hosted CI, deploy is the safe default — it does not run any git commands on the runner.
Use the strut-version input to pin to a specific release tag, ensuring reproducible deploys:
- uses: gfargo/strut-action@v1
with:
strut-version: v0.45.0
# ...The action installs strut at that tag and logs the version to the action output.
The env-file input takes the entire contents of the env file, written to .<env>.env before the strut command runs. Store the whole file as one secret:
- uses: gfargo/strut-action@v1
with:
env-file: ${{ secrets.STRUT_PROD_ENV }}
# ...Where STRUT_PROD_ENV holds the file verbatim:
GH_PAT=ghp_…
REGISTRY_TOKEN=…
The file is written with 600 permissions and never echoed to the action log.
This input takes a whole file, not a list of
KEY=VALUEadditions. Connection values (host,user,port) are merged in separately by the action, so you do not need to repeat them here.
- The SSH key is written to
$RUNNER_TEMP/strut_deploy_keywith mode600and never printed. - The env file is written with mode
600. Values are materialized viaprintf, notecho. -
VPS_HOSTis masked in the action log with::add-mask::. - Secrets passed as
${{ secrets.* }}are automatically redacted by GitHub Actions. - strut uses
StrictHostKeyChecking=no(consistent with its standard SSH behavior) — noknown_hostspinning. -
--dry-runshows the SSH plan (host/user only) without leaking key or env values.
strut: command not found after install
Install adds a symlink to /usr/local/bin (writable on hosted runners) or ~/.local/bin. If the Verify strut version step fails, the runner environment may be unusual — open an issue with the runner OS and version.
Not inside a strut project
The runner must have strut.conf at the repo root. Ensure actions/checkout runs before this action.
Stack not found
The stack input must match a directory under stacks/ in your repository.
Deploy hangs or fails on SSH
Verify STRUT_HOST resolves, port 22 (or your port) is open, and the public key matching STRUT_SSH_KEY is in ~/.ssh/authorized_keys on the VPS.
deploy deployed nothing, but the job passed
The stack resolved to no VPS, so the deploy fell back to the runner's own Docker daemon. The action passes --require-remote to turn this into a hard failure — if you invoke strut directly in a run: step, pass it yourself. Check that the stack is mapped under [stacks] in strut.conf or that VPS_HOST is set for the environment.
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