-
Notifications
You must be signed in to change notification settings - Fork 0
Network & Connectivity
This page covers how GitHub Actions runners connect to the NAS sitting on a private home network — and the security decisions behind it.
GitHub-hosted runners are ephemeral VMs on Microsoft's infrastructure. My NAS sits on a private network behind a router. How do we bridge that gap securely?
Each workflow run establishes a temporary WireGuard tunnel from the runner to my home network. This gives the runner a routable path to the NAS without exposing any services to the public internet.
flowchart LR
subgraph GitHub Actions Runner
WF[Workflow<br/>ephemeral]
end
WF <-->|WireGuard<br/>encrypted tunnel| WGP
subgraph Home Network
WGP[WG Peer<br/>router/endpoint] -->|LAN| NAS[NAS]
end
- Simple — single config file, minimal dependencies
- Fast — connects in milliseconds (critical for CI/CD where every second counts)
- Secure — modern cryptography, minimal attack surface
- Stateless — no "session" to keep alive between workflow runs
- name: Connect to Wireguard
run: |
sudo apt-get install -y wireguard resolvconf
echo "${{ secrets.WG_CONFIG_FILE }}" > wg0.conf
sudo wg-quick up ./wg0.conf
- name: Add Routes
run: |
sudo ip route add ${{ vars.HOST_IP }} dev wg0The full WireGuard configuration (interface private key, peer public key, endpoint, allowed IPs) is stored as a single GitHub Secret (WG_CONFIG_FILE). This keeps the config atomic — you either have access or you don't.
Once the tunnel is up, the workflow connects to the NAS via SSH for:
- nas-as-code: Running Ansible playbooks
- nas-containers-as-code: Docker-over-SSH (remote Docker context)
Rather than using ssh-keyscan (which trusts whatever host key the network hands it — a MITM risk), I pin the NAS's SSH host fingerprint as a GitHub Secret (HOST_FINGERPRINT).
- name: Add SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
echo "${{ secrets.HOST_FINGERPRINT }}" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
⚠️ If the NAS is ever rebuilt and its SSH host keys regenerate, you must update theHOST_FINGERPRINTsecret or the workflow will refuse to connect. This is intentional — it forces you to consciously acknowledge the new host identity.
For the container deployment (which makes many Docker API calls over SSH), I enable SSH connection multiplexing to avoid hitting the NAS's MaxSessions limit:
Host *
ControlMaster auto
ControlPath ~/.ssh/control-%C
ControlPersist 10m
This keeps a single SSH connection open and multiplexes all subsequent commands over it. Without this, the media stack (13+ services) would blow past the default MaxSessions=10 and fail with "Session open refused by peer".
| Layer | Mechanism | Protects Against |
|---|---|---|
| Network | WireGuard tunnel | Eavesdropping, unauthorised access |
| Host Identity | Pinned fingerprints | MITM / host impersonation |
| Authentication | SSH private key (in GitHub Secret) | Unauthorised SSH access |
| Session | Ephemeral runner | No persistent credentials on disk |
Nothing is exposed to the public internet. The WireGuard endpoint is the only inbound port, and it only responds to peers with the correct pre-shared key.
Overview
Secrets & Networking
Ansible
- Ansible Structure
- Role: Docker
- Role: Storage
- Role: Samba
- Role: NUT Client
- Role: Backup
- Role: Coral TPU
Pipelines
Operations