GitHub Actions Egress Costs, Explained
Egress charges from GitHub Actions land on your cloud bill instead of GitHub's invoice. Where they come from, how to cut them, and a worked AWS model.
Last verified:
Egress charges from GitHub Actions never appear on the GitHub invoice. They land on your cloud bill, because every deploy job pulls container images out of ECR, artifacts out of S3, and caches out of object storage across a metered path, and the meter counts gigabytes rather than jobs. This guide shows where those charges sit on an AWS bill, which workflow changes cut them this week, and what the WarpBuild enterprise tier removes for teams that want the line gone entirely.
Diagnosis
Start by separating the two invoices. GitHub bills GitHub Actions minutes and artifact storage. Your cloud provider bills the bytes that cross its network boundary. A team that reads only the GitHub invoice concludes that GitHub Actions is cheap, then finds a data transfer line on the cloud bill that grew every quarter for reasons nobody attributed to a workflow.
Four charge shapes cover most of it.
Image pulls from ECR on every deploy job
A deploy job authenticates to ECR, pulls the application image, and runs it or promotes it. When the runner sits inside your account and Region, AWS states that data transferred between Amazon ECR and other services in the same Region, including Amazon EC2, AWS Lambda, AWS App Runner, and AWS Fargate, is free of charge at $0.00 per GB (Amazon ECR pricing, checked on 2026-08-13).
When the runner sits outside your account, that same pull leaves the Region. AWS states that data transferred between Amazon ECR and other services in different Regions is charged at internet data transfer rates on both sides of the transfer, and its own worked example on that page uses $0.09 per GB (checked on 2026-08-13). Nothing about the workflow changed. The placement of the machine changed, and a free pull became a metered one.
This is the charge that scales fastest, because image size and deploy frequency both grow over time. A 1.4 GB image pulled by 40 deploy jobs a day moves 56 GB a day out of the Region.
Artifact and cache downloads from S3
Teams that outgrew actions/upload-artifact usually push build outputs, test fixtures, or model weights into their own S3 bucket and pull them back in later jobs. Same shape as the registry: reads from a runner in the same Region cost nothing, reads from a runner outside it are billed at internet data transfer rates, and the first 100 GB of data transfer out to the internet each month is free aggregated across all AWS services and Regions (Amazon EC2 On-Demand pricing, checked on 2026-08-13). That free allowance is shared with production, so a busy account has usually spent it before GitHub Actions asks for any.
Cross-AZ and cross-Region transfer inside your own account
Self-hosted fleets often drift. The registry lives in us-east-1, someone adds capacity in us-west-2 for instance availability, and now the pull crosses a Region boundary and is priced as internet transfer on both sides. The same drift happens at the Availability Zone level when runner subnets and the services they read sit in different zones inside one Region. Check the AWS pricing pages linked above for the current per-GB rate on each path, because the rate depends on the exact path and Region pair.
Managed NAT data processing
Runners that need a stable outbound IP address get placed in private subnets, and traffic then leaves through a managed NAT service. In US East (N. Virginia), AWS charges $0.045 per hour for the gateway plus $0.045 per GB of data processed, in both directions (Amazon VPC pricing, checked on 2026-08-13). The WarpBuild BYOC documentation calls this out directly: enabling static IPs puts runners in private subnets and adds data processing fees on every gigabyte a job moves, so static IPs are worth enabling only for the workflows that actually require an allowlisted address.
How to confirm it on your own bill
Open Cost Explorer, group by Usage Type, and look for DataTransfer-Out-Bytes, DataTransfer-Regional-Bytes, and the NatGateway-Bytes usage types. Then filter by the account or tag that carries your runner fleet. Two signatures tell you the charge belongs to GitHub Actions rather than production:
- The daily curve tracks merge volume. It rises Monday through Thursday, falls on weekends, and spikes on release days.
- The curve does not track end-user traffic. Production request volume stays flat while transfer climbs.
If both hold, the workflow is the customer of that line item.
Fix
Three moves, in the order that pays back fastest.
Pull fewer gigabytes
Most deploy images carry build tooling that the runtime never uses. A multi-stage Dockerfile that copies only the compiled output into a slim runtime base cuts what every future job pulls, forever. Pin base images by digest so a silent upstream retag stops invalidating layers across your whole fleet.
Registry-backed layer cache is the second lever. When cache-from and cache-to point at a cache tag in the same registry, unchanged layers stop moving on every build.
The third lever is fan-out. A matrix with six legs that each pull the same 1.4 GB image moves 8.4 GB per run. Pull once in a setup job, push the digest through job outputs, and let the legs pull only what differs. The build-side version of the same idea is covered in Docker builds on GitHub Actions.
Cache more inside the runner boundary
WarpBuild Linux runners ship an unlimited cache that is enabled by default, so dependency restores stay on the runner side of the boundary rather than becoming another metered download. Cache storage is billed at $0.20 per GB-month and cache write or restore operations at $0.0001 each on the WarpBuild pricing page, which is the trade you are making against per-GB transfer.
Colocate the runner with the data
This is the structural fix. Run the runners inside the account and Region that already holds the registry and the buckets, and the largest line on the model below goes to $0.00 per GB by AWS's own pricing. BYOC runs on AWS, GCP, and Azure, and Terraform support exists for BYOC on AWS, so the placement can live in the same module as the rest of your infrastructure. The setup steps are in the guide to running GitHub Actions runners in your own AWS account.
Colocation leaves one residue: runners in private subnets still pay managed NAT data processing on everything they move, including package registries and the GitHub API. Keep the fleet in public subnets unless an allowlisted static IP is a hard requirement.
The enterprise outcome
Teams that want this line off the bill entirely qualify on the WarpBuild enterprise tier; the scope and the worked model are on zero egress for the enterprise tier.
Configuration
A deploy job that moves less data
The workflow below builds and pushes on a WarpBuild Linux runner, keeps layer cache in the same registry as the image, and never rebuilds a layer it already has. The runs-on value is the only line that differs from a GitHub-hosted setup.
name: deploy
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: warp-ubuntu-latest-x64-4x
permissions:
id-token: write
contents: read
outputs:
image: ${{ steps.push.outputs.imageid }}
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy
aws-region: us-east-1
- uses: aws-actions/amazon-ecr-login@v2
id: ecr
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
id: push
with:
push: true
tags: ${{ steps.ecr.outputs.registry }}/api:${{ github.sha }}
cache-from: type=registry,ref=${{ steps.ecr.outputs.registry }}/api:buildcache
cache-to: type=registry,ref=${{ steps.ecr.outputs.registry }}/api:buildcache,mode=maxStop the matrix from pulling the same image six times
The smoke job below reads the digest that the build job already resolved, so each matrix leg pulls exactly one image reference and the registry serves cached layers rather than a fresh copy per leg.
smoke:
needs: build-and-push
runs-on: warp-ubuntu-latest-x64-2x
strategy:
matrix:
suite: [api, worker, web]
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy
aws-region: us-east-1
- uses: aws-actions/amazon-ecr-login@v2
id: ecr
- run: docker pull "${{ steps.ecr.outputs.registry }}/api@${{ needs.build-and-push.outputs.image }}"
- run: ./scripts/smoke.sh ${{ matrix.suite }}Move the same job into your own account
BYOC runners register with a warp-custom- prefix followed by the runner name you chose in the dashboard. Setting the stack Region to the Region that holds the registry is what turns the pull into same-Region traffic.
build-and-push:
runs-on: warp-custom-deploy-use1Runner labels and rates used above
Linux x64 labels from the WarpBuild catalog, with per-minute rates from the pricing page:
| Runner label | OS | vCPU | RAM | Storage | Price per minute |
|---|---|---|---|---|---|
| warp-ubuntu-latest-x64-2x | Ubuntu 24.04 | 2 | 8 GB | 150GB SSD | $0.004 |
| warp-ubuntu-latest-x64-4x | Ubuntu 24.04 | 4 | 16 GB | 150GB SSD | $0.008 |
| warp-ubuntu-latest-x64-8x | Ubuntu 24.04 | 8 | 32 GB | 150GB SSD | $0.016 |
| warp-ubuntu-latest-x64-16x | Ubuntu 24.04 | 16 | 64 GB | 150GB SSD | $0.032 |
| warp-ubuntu-latest-x64-32x | Ubuntu 24.04 | 32 | 128 GB | 150GB SSD | $0.064 |
BYOC runners are billed at $0.002 per minute in WarpBuild fees on Linux, with the compute billed by your own cloud account. The full label set is in the cloud runners documentation.
Cost or Time Model
Assumptions
Every input below is stated so you can substitute your own. Rates are AWS list prices for US East (N. Virginia), checked on 2026-08-13.
| Input | Value | Source |
|---|---|---|
| Deploy jobs per weekday | 40 | Your workflow run history |
| Weekdays per month | 22 | Calendar |
| Image bytes pulled per job | 1.4 GB | Your registry metrics |
| Artifact and cache bytes pulled per job | 0.2 GB | Your bucket metrics |
| Deploy job duration | 6 minutes | Your workflow run history |
| Internet data transfer rate | $0.09 per GB | Amazon ECR pricing |
| Free internet transfer allowance | 100 GB per month, all services | Amazon EC2 On-Demand pricing |
| ECR to same-Region compute | $0.00 per GB | Amazon ECR pricing |
| Managed NAT data processing | $0.045 per GB | Amazon VPC pricing |
| Managed NAT hourly charge | $0.045 per hour | Amazon VPC pricing |
| GitHub-hosted ubuntu-latest, private repositories | $0.006 per minute | GitHub Actions billing |
The arithmetic
Monthly deploy jobs: 40 times 22 equals 880.
Bytes pulled per job: 1.4 GB plus 0.2 GB equals 1.6 GB. Monthly total: 880 times 1.6 equals 1,408 GB.
Runner minutes: 880 times 6 equals 5,280 minutes. At GitHub's published rate of $0.006 per minute for ubuntu-latest on private repositories, that is $31.68 of GitHub Actions minutes per month (GitHub billing reference, checked on 2026-08-13).
Now price the same 1,408 GB three ways.
| Runner placement | Transfer charge | Managed NAT charge | Monthly total |
|---|---|---|---|
| Outside your account, pulling from ECR over the internet path | 1,308 GB billable times $0.09 equals $117.72 | $0.00 | $117.72 |
| Inside your account and Region, public subnets | 1,408 GB times $0.00 equals $0.00 | $0.00 | $0.00 |
| Inside your account and Region, private subnets with a managed NAT | $0.00 | 1,408 GB times $0.045 equals $63.36, plus 730 hours times $0.045 equals $32.85 | $96.21 |
The billable figure in row one credits the 100 GB monthly allowance once. If production already consumed it, add $9.00 to that row.
What the model says
The transfer line for this workflow is $117.72 a month against $31.68 of GitHub Actions minutes at GitHub's list price. The bytes cost more than the compute, and no dashboard in GitHub shows it, which is why the charge survives so many cost reviews.
Placement is the variable that moves it. Moving the same 880 jobs into the account and Region that already holds the registry takes the $117.72 to zero by AWS's own published rate, and leaves $96.21 of managed NAT charges if the fleet needs static IPs. Turning static IPs off for the jobs that do not need an allowlisted address clears that residue too.
Scale the model linearly for your own numbers. A team running 200 deploy jobs a day with a 3 GB image moves 13,200 GB a month, which is $1,179.00 at $0.09 per GB after the free allowance. Once the transfer line is handled, the minute line is the next one to work on, and reducing GitHub Actions costs across runners and workflows covers that side.
Where WarpBuild fits
WarpBuild pricing is purely usage based. There is no base subscription fee, no platform fee, and no seat fee, so the runner side of this model is per-minute rates against the labels in the table above. Signup includes $10 free credits. At $0.008 per minute for a 6 minute build job on warp-ubuntu-latest-x64-4x, that covers just over 1,000 runs of the deploy job above while you compare Cost Explorer before and after. Matched shape for shape, warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) costs $0.008 per minute against $0.012 per minute for the 4-core Linux larger runner (4 vCPU, 16 GB): 33 percent lower list price. GitHub list price checked on 2026-08-13.
For the transfer side, two options exist. BYOC puts the runners in your own AWS, GCP, or Azure account, so pulls become same-account, same-Region traffic priced by your cloud provider. The enterprise tier is the other path, covered on zero egress on the enterprise tier; talk to engineering with your transfer numbers in hand.
Cloud list prices change. Re-check the four AWS links and the GitHub billing reference before you quote these totals internally, and re-run the arithmetic with your own image size and job count.
FAQ
Does GitHub charge for egress on GitHub Actions?
No. A GitHub Actions invoice bills minutes and artifact storage. Data transfer charges appear on your cloud bill instead, because the bytes leave your cloud account when a job pulls an image or an artifact.
Why did our AWS transfer bill grow when we started deploying more often?
Transfer charges scale with the number of deploy jobs. Production request volume has no effect on them. Every job pulls the image and the artifacts again, so doubling merges per day doubles the gigabytes pulled out of ECR and S3.
Do self-hosted runners remove data transfer charges?
Only when they sit in the same account and Region as the registry and buckets they read. AWS prices data transferred between Amazon ECR and Amazon EC2 in the same Region at $0.00 per GB, and runners placed in private subnets still pay managed NAT data processing at $0.045 per GB in US East (N. Virginia). Both figures checked on 2026-08-13.
Can egress from our cloud reach zero?
On the enterprise tier, yes. The scope, the platforms it covers, and a worked cost model are on zero egress for the enterprise tier.
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.