Can GitHub Actions Runners Reach My Private Network?

Yes. The networking addon joins each WarpBuild runner to your Tailscale tailnet at job start as an ephemeral node that is removed when the job ends.

Last verified:

Yes. WarpBuild runners reach private services through the networking addon, which joins the runner to your Tailscale tailnet at job start as an ephemeral node and removes that node when the job finishes. Setup is two pieces: a network addon configuration holding your Tailscale OIDC client ID, then a network.name=<config-name> modifier appended to the runs-on label, both documented in the networking addon reference.

Answer

A GitHub Actions job gets private network access when the machine running it is a member of the private network. The networking addon makes each runner a member for the lifetime of the job.

The sequence, from the networking addon reference:

  1. You create a network addon configuration in the WarpBuild dashboard under Addons > Networking, naming it and pasting the OIDC client ID from a Tailscale trust credential.
  2. Your workflow adds network.name=<config-name> to the runs-on label.
  3. At job start, WarpBuild authenticates the runner to your tailnet with a short-lived OIDC token and an ephemeral node key.
  4. The runner joins the tailnet and reaches any device or service the ACL policy allows.
  5. When the job finishes, the ephemeral node is removed from the tailnet.

The workflow change is one line:

name: deploy

on:
  push:
    branches: [main]

jobs:
  migrate:
    runs-on: warp-ubuntu-latest-x64-4x;network.name=production-tailnet
    steps:
      - uses: actions/checkout@v4

      - name: Check the internal API is reachable
        run: curl -fsS https://internal-api.myorg.tailnet.ts.net/health

      - name: Run database migrations
        run: |
          psql "postgres://db.myorg.tailnet.ts.net:5432/mydb" -f migrations.sql

      - name: Publish to the internal package registry
        run: npm publish --registry https://registry.myorg.tailnet.ts.net

The runner label and the addon modifier are separated by a semicolon, so the same modifier attaches to any label in the catalog. The addon works on all four.

Two properties matter for the security review of this setup. The runner joins as an ephemeral node, so nothing lingers in your tailnet device list after the job. Authentication uses a short-lived OIDC token signed by WarpBuild rather than a long-lived auth key stored in a secret, so there is no static credential to rotate or leak.

Detail

The addon configuration and what goes in it

The configuration lives in WarpBuild and holds three fields.

Name. A descriptive string such as production-tailnet. This is the value you write after network.name= in the workflow label, so pick something short and stable.

Provider. Tailscale.

OIDC client ID. Generated by Tailscale when you add a trust credential of type OIDC. The issuer is the custom issuer https://api.warpbuild.com, and the subject is the wildcard warp-*, because WarpBuild issues tokens with sub set to the runner instance ID and every instance ID carries the warp- prefix. On the scopes step, the credential needs read and write on auth_keys under Keys > Auth Keys, which is what allows ephemeral auth key creation for node registration. Every other scope stays unchecked.

The client ID is a public identifier used to start the OIDC flow, so it does not need to live in a secret. The security of the exchange comes from the signed token, not from the ID.

Additional arguments. An optional string of flags passed through to tailscale up.

FlagEffect
--advertise-tags=tag:ciApplies ACL tags to the runner node
--accept-routesAccepts subnet routes advertised by other nodes on the tailnet
--accept-dns=falseTurns off Tailscale DNS overrides on the runner
--advertise-routes=10.0.0.0/24Advertises routes from the runner into the tailnet

Flags combine in a single string, for example --advertise-tags=tag:ci --accept-routes --accept-dns=false. Any tag you pass through --advertise-tags has to be defined in your Tailscale ACL policy and listed under the trust credential's Tags section, otherwise registration fails.

Reserved flags

Five flags are managed by WarpBuild and are rejected if you supply them in additional arguments:

Reserved flagWhy WarpBuild owns it
--hostnameDerived from the runner instance ID so nodes stay identifiable per job
--auth-keyCreated per job as an ephemeral key through the OIDC exchange
--client-idTaken from the addon configuration
--id-tokenThe short-lived token WarpBuild signs for the runner
--statePoints at the state path the setup script controls

The practical read is that you control policy and routing, and WarpBuild controls identity and lifecycle. Attempts to override identity or lifecycle fail the configuration rather than silently taking effect.

Platform coverage

Runner platformNetworking addonExample label
Linux, Ubuntu x64Supportedwarp-ubuntu-latest-x64-4x
Linux, Ubuntu ARM64Supportedwarp-ubuntu-latest-arm64-4x
macOS, ARM64Supportedwarp-macos-latest-arm64-6x
Windows, x64Supportedwarp-windows-latest-x64-4x

Rows come from the networking addon reference, checked on 2026-08-13. Tailscale is pre-installed on every WarpBuild runner image, and the setup script starts the daemon and authenticates on demand, so jobs that do not request the addon never start it. BYOC runners work the same way, including AWS, GCP, and Azure. If you build custom VM images for BYOC, install jq in the image; the setup script installs Tailscale itself when it is missing.

Failure behavior

If network setup fails, the job fails. That is the documented behavior and it is the one you want: a deploy job that cannot reach the internal database should stop rather than run half its steps against whatever it can reach. Build the assumption into your workflow design by keeping the private-network work in jobs that carry the network.name modifier and leaving public work on plain labels.

Scoping what a runner can reach

Tailnet membership on its own is broad, so the access control belongs in your ACL policy. Tag the runner node, then write rules against the tag:

{
  "tagOwners": {
    "tag:ci": ["autogroup:admin"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["tag:ci"],
      "dst": ["tag:internal-api:443", "tag:db:5432"]
    }
  ]
}

With that policy in place, a job running under --advertise-tags=tag:ci reaches the internal API on 443 and the database on 5432 and nothing else on the tailnet. Different job classes can carry different tags by pointing at different addon configurations, which is how you keep a test job away from production data stores.

Combining the addon with other label modifiers

Modifiers chain on one runs-on line, so private network access composes with snapshot restore:

jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x;network.name=production-tailnet;snapshot.key=deps-v3

  integration:
    runs-on: warp-ubuntu-latest-x64-8x;network.name=production-tailnet;snapshot.enabled=true

Infrastructure workflows are the other common pairing. A terraform apply job that talks to a private state backend or an internal provider API carries the same modifier as any other job, and the shape of those pipelines is covered in Terraform workflows on GitHub Actions.

What the addon does not cover

The networking addon covers Tailscale tailnet membership. It does not hand a runner a fixed public egress address, and it is not an egress allowlist. Teams that need a stable source address for a partner firewall rule get it a different way: run BYOC runners inside your own cloud account and configure the subnets there, which is covered in the BYOC AWS security hardening docs and in the BYOC on AWS overview. The two capabilities stack, since the addon works on BYOC runners as well. For the wider decision between joining a tailnet and placing the runners themselves inside your own network, see running GitHub Actions runners inside your own VPC.

What a private-network workflow costs

Take a deploy pipeline that runs 1,200 jobs a month at 8 minutes each on a 4 vCPU Linux runner, every job joining the tailnet to reach an internal database and an internal registry. That is 9,600 minutes.

Line itemRateMonthly
warp-ubuntu-latest-x64-4x, 9,600 minutes$0.008 per minute$76.80
Networking addon$0 per minute$0.00
GitHub-hosted 4-core Linux larger runner, same 9,600 minutes$0.012 per minute$115.20

WarpBuild rates come from the pricing page. The GitHub rate comes from the GitHub Actions billing reference, checked on 2026-08-13. The arithmetic is (0.012 - 0.008) / 0.012, which is 33 percent lower list price at the same 4 vCPU and 16 GB shape.

Every cost claim on this page carries the number, the source link, and the checked-on date, and the same numbers appear on the pricing page.

Which runner platforms support the networking addon?

Linux x64, Linux ARM64, macOS, and Windows. Tailscale is pre-installed on every WarpBuild runner image, and the setup script starts the daemon only when a job requests the addon. Label shapes for all four platforms are in the networking addon reference.

What happens if the Tailscale setup fails?

The job fails. Network setup runs before your steps, so a workflow never runs silently without the private access it expects.

Can different jobs join different tailnets?

Yes. Create one network addon configuration per tailnet and reference the one you want with network.name=<config-name> on the runs-on label of each job. The same pattern separates production access from staging access.

Does the networking addon give my runners a static egress IP?

No. The addon covers Tailscale tailnet membership only. Static egress IPs come from subnet configuration inside your own cloud account on BYOC runners, which is a separate capability described in the BYOC AWS security hardening docs.

Wire up the addon with the steps in the networking addon reference, size the jobs against your own minutes on the pricing page, and read the security review material if a reviewer needs the runner isolation story in writing.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.