-
Notifications
You must be signed in to change notification settings - Fork 0
Deployment Pipeline
The GitHub Actions workflow that deploys all Docker stacks to the NAS.
on:
push:
branches: [main]
paths-ignore: ['**/*.md']Runs on every push to main (excluding markdown-only changes).
| # | Step | Purpose |
|---|---|---|
| 1 | Checkout | Get the code |
| 2 | Azure Login | Authenticate using SPN |
| 3 | Get KeyVault Secrets | Pull all secrets, export as masked env vars |
| 4 | Replace Tokens | Substitute #{TOKEN}# in .env files with secret values |
| 5 | Connect to WireGuard | Establish VPN tunnel |
| 6 | Add Routes | Route NAS IP over WireGuard |
| 7 | Add SSH Key | Install key + pinned host fingerprint |
| 8 | Configure SSH | Multiplexing + keepalives |
| 9 | Create Docker Context | Point Docker CLI at NAS over SSH |
| 10 | Deploy Stacks | Loop through directories, docker compose up -d
|
| 11 | Sync to NAS | Rsync repo content to /config/stacks
|
🔗 For details on steps 2-8 (secrets, VPN, SSH), see the Secrets & Credentials and Network & Connectivity pages in the nas-as-code wiki.
Rather than exposing the Docker API over TCP (a security risk), the workflow creates a Docker context that tunnels commands over SSH:
- name: Connect to NAS (Docker Daemon)
run: |
docker context create NAS --docker "host=ssh://${{ vars.HOST_USER }}@${{ vars.HOST_IP }}"
docker context use NAS
docker infoThis means every docker compose command is executed remotely on the NAS, but initiated from the GitHub Actions runner.
The workflow sets COMPOSE_PARALLEL_LIMIT=3:
env:
COMPOSE_PARALLEL_LIMIT: 3Why? Docker Compose normally runs container operations in parallel. Over SSH, each operation opens multiple SSH channels. The NAS's sshd has a default MaxSessions=10. Without this limit, larger stacks (like media with 13 services) blew past that cap with:
Session open refused by peer
Setting the limit to 3 keeps channels comfortably below 10.
The workflow iterates through every directory containing a docker-compose.yml:
for dir in */; do
[ -f "${dir}docker-compose.yml" ] || continue
stack="${dir%/}"
cd "$dir" && docker compose up -d
doneIf any stack fails, it's recorded but the loop continues — other stacks still deploy. The workflow fails at the end if any stack failed.
After deployment, the repo content is rsync'd to /config/stacks on the NAS. This means the local backup tooling (and Dockhand) can reference the same compose files without needing Git access.
.env files contain #{TOKEN}# patterns that are replaced with Azure KeyVault secret values before deployment. The cschleiden/replace-tokens action handles this:
- name: Replace tokens in .env files
uses: cschleiden/replace-tokens@v1
with:
files: '["**/.env"]'💡 The
.envfiles in the repo contain tokens, not secrets. Secrets only exist in the runner's environment variables during the workflow run.
A separate workflow runs on all branches except main (i.e. on PRs and feature branches) to catch issues before they reach production:
| Check | Tool | Purpose |
|---|---|---|
| Docker Compose validation | Pester tests | Validates all docker-compose.yml files are syntactically correct |
| Security scan | Checkov | Infrastructure-as-code security best practices |
| Security scan | Trivy | Container vulnerability detection |
| SARIF upload | CodeQL | Surfaces security findings in the GitHub Security tab |
This means broken compose files or known vulnerable configurations are caught before merge — the deploy workflow only runs on main after validation passes.
Overview
How-To