A GitHub Action that installs the NetBird client on an Ubuntu runner and joins your network with a setup key, so the rest of the job can reach your private peers. It can also route the runner's traffic through an exit node.
- In the NetBird dashboard, open Settings-> Setup Keys and create one for your runners:
- Turn on Ephemeral Peers.
- One-off if a single job uses it, reusable otherwise.
- Set a proper expiry
- Give it a group your access policies already allow, so the runner can reach what it needs.
- Add the key as a repository secret named
NETBIRD_SETUP_KEY.
Important
The key must be ephemeral. The action leaves the peer registered when the job ends β see Cleanup β so without it every run adds a peer to your dashboard for you to delete by hand.
Warning
Never commit the setup key or pass it as a plain string β anyone holding it can register a peer on your network.
The only input you need is setup-key. Everything else below is optional and shown at its default, apart from
exit-node and dns-hostnames, which do nothing until you set them:
name: Testing
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Connect to the NetBird network
id: netbird
uses: ankurk91/netbird-action@v2
with:
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
# Point this at your own deployment when you self-host.
management-url: https://api.netbird.io:443
# Peer name in the dashboard. Every leg of a matrix shares one run id,
# so give those a name of their own.
peer-name: gh-${{ github.run_id }}-${{ github.run_attempt }}
# Network ID of the route to send traffic through. Off when empty.
exit-node: ${{ vars.NETBIRD_EXIT_NODE_ID }}
# Names that must resolve before this step finishes. Off when empty.
dns-hostnames: ''
# Only accept addresses inside your network.
dns-require-private: true
# Extra flags for the NetBird client.
args: ''
# Client release to install. Pin it to keep runs reproducible.
version: latest
# How long to wait for the peer, the route and the DNS names.
timeout: 60
# Undo everything when the job ends. Only needed on self-hosted.
cleanup: false
diagnostics: false
# From here the runner is a peer and can reach the others.
- name: Do work on the private network
run: |
echo "this runner is ${{ steps.netbird.outputs.netbird-ip }} on the network"
curl -s http://internal-service.netbird.cloudThere is no disconnect step to add β see Cleanup.
Warning
An exit node carries 0.0.0.0/0, so the runner's connection to GitHub goes through it too. If the exit node cannot
reach GitHub, the job hangs after this step rather than failing.
| Input | Required | Default | Description |
|---|---|---|---|
setup-key |
yes | β | Setup key from the dashboard. Always pass this from a secret. |
management-url |
no | https://api.netbird.io:443 |
Management service URL. Set this when you self-host NetBird. |
peer-name |
no | gh-<run id>-<run attempt> |
Peer name shown in the dashboard. |
exit-node |
no | β | Network ID to route through. The route must be distributed to this peer's group. |
dns-hostnames |
no | β | Names that must resolve before the action finishes. See Waiting for DNS. |
dns-require-private |
no | true |
Only accept a dns-hostnames name that points inside your network. |
args |
no | β | Extra flags appended to netbird up, split on whitespace - one per line works. |
version |
no | latest |
Client release to install. See Client version. |
github-token |
no | ${{ github.token }} |
Raises the API rate limit when version is pinned. Only sent then. |
timeout |
no | 60 |
Seconds to wait for the peer, the exit node route, and the DNS names. |
cleanup |
no | false |
Undo everything when the job ends. See Cleanup. |
diagnostics |
no | false |
Print the peer state to the job log. See Diagnostics. |
| Output | Description |
|---|---|
netbird-ip |
The runner's IPv4 address inside the NetBird network, e.g. 100.64.0.33. |
This is the runner's address inside your network β what your other peers use to reach it. It is not the runner's public IP.
Being connected is not the same as being able to resolve your private hostnames. If the next step in your job reaches a service by name, list those names and the action waits until they work before it hands over:
- uses: ankurk91/netbird-action@v2
with:
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
dns-hostnames: postgres.netbird.cloud, internal-service.netbird.cloudBy default, a name only counts as ready once it points inside your network, so a step cannot quietly talk to a public
endpoint when it meant to reach a private one. Set dns-require-private: false for a name that is supposed to answer
publicly.
Without this the action still waits for the peer to connect β it just does not check that your names resolve.
A GitHub-hosted runner is destroyed when the job ends, and your ephemeral setup key takes the peer out of the dashboard once it has been offline for ten minutes.
Self-hosted runners are the exception, because the machine outlives the job β set cleanup: true there, or the next job
on it inherits a network it never asked to join.
- An Ubuntu runner (
ubuntu-latest,ubuntu-24.04,ubuntu-26.04, their-armvariants, or self-hosted Ubuntu). - NetBird client 0.67.0 or newer.
Leave version at latest for the newest client, or pin it (0.78.1) to keep every run identical. The minimum is
0.67.0. A client already installed on the runner is kept as it is.
diagnostics: true prints the peer's state to the job log while you work out why a connection is failing.
It is off by default because that output describes your private network, and job logs reach more people than your dashboard does. Failures print an anonymised summary either way.
Upgrading? See CHANGELOG.md.
Something not working? See TROUBLESHOOTING.md.
Curious how any of this behaves under the hood? See HOW-IT-WORKS.md.