Egress Costs

Egress costs are the per-gigabyte charges a cloud provider applies to data leaving its network or crossing its zones and regions. Where they come from.

Egress costs are the charges a cloud provider applies to data that leaves its network, and to data that crosses a boundary inside it such as an availability zone or a region. They are metered per gigabyte moved rather than per request or per machine hour, so the charge follows the volume of bytes a system moves and appears on the cloud bill under a data transfer heading.

The term shows up in GitHub Actions discussions because build and deploy jobs are heavy readers of cloud storage. A job that pulls a container image, a model file, or a release archive is moving gigabytes out of a store on a schedule set by how often a team merges.

Definition

Egress is the outbound direction of data movement, measured at a boundary the provider defines. Egress cost is what the provider charges for crossing that boundary. Three properties describe it in every catalog.

The unit is the gigabyte. Providers publish a rate per GB, apply it to the volume that crossed the boundary during the billing period, and the resulting line has no relationship to the number of requests, instances, or jobs involved. Ten thousand small reads and one large read of the same total volume produce the same charge.

The direction is asymmetric. Data arriving from outside is normally accepted at no charge, and data leaving is metered. The asymmetry is why a workload that mostly writes into a cloud looks cheap and a workload that mostly reads out of one does not.

The boundary is defined by the provider, not by intuition. The public internet is the obvious boundary. Providers also meter movement between their own regions, and often between availability zones inside a single region, so bytes that never touch the internet can still be billed as transfer.

The three usual charge types

Provider catalogs differ in naming, and the same three shapes recur.

Internet egress. Bytes leaving the provider network toward the public internet or toward another provider. This is the highest published per-GB rate of the three and the one most people mean by the word egress. Providers commonly grant a small monthly allowance before the meter starts.

Cross-region and cross-zone transfer. Bytes moving between two regions of the same provider, or between availability zones inside one region. The rates are lower than internet egress and the volumes are easy to generate by accident, because zone and region placement is a deployment detail rather than something a workload declares.

NAT data processing. A per-GB fee for traffic passing through a managed network address translation service, usually alongside an hourly charge for the device itself. This one is different from the other two in an important way: the processing fee applies in both directions, so inbound bytes that would otherwise be free become metered once they route through NAT.

A fourth item sits next to these without being egress: request or operation charges on object storage. Those meter API calls rather than gigabytes, and a workload with many small objects can pay more in operations than in transfer.

A published rate card, as one concrete example

Rates below are AWS list prices for US East (N. Virginia), quoted with the page they come from and checked on 2026-08-13. Every provider publishes an equivalent table, and the rates move, so treat this as the shape of the data rather than a constant.

PathPublished rateCharge typeSource
Registry to compute in the same Region$0.00 per GBSame-Region transferAmazon ECR pricing
Registry to a destination in a different RegionInternet data transfer rates on both sides, priced at $0.09 per GB in the worked example on that pageCross-region transferAmazon ECR pricing
Out to the internet, first 100 GB each month$0.00 per GB, aggregated across services and RegionsInternet egress allowanceAmazon EC2 On-Demand pricing
Managed NAT, data processed$0.045 per GB, both directionsNAT data processingAmazon VPC pricing
Managed NAT, device time$0.045 per hourFixed hourly chargeAmazon VPC pricing

Two readings of that table matter. The first row costs nothing, and the second row is the same operation with the reader placed somewhere else, so placement rather than payload decides whether a meter runs at all. The last two rows show that a network design choice, putting machines in private subnets behind a managed NAT, adds a per-GB charge to traffic that had none.

Some paths can be taken off the meter by keeping the read inside the account network instead of routing it over the public internet. The WarpBuild BYOC AWS configuration guide recommends configuring S3 access directly on the VPC so machines reach the bucket without incurring data transfer charges, and notes that the bucket should sit in the same region as the stack.

Who bills it

The provider that owns the network bills the egress. That is worth stating because it explains why the charge is easy to miss. An invoice for a source control or build service lists minutes and storage. The gigabytes moved by those minutes are billed separately by whichever cloud held the data, mixed into a data transfer line alongside production traffic.

The two invoices also have different owners inside most companies, so nobody is looking at both at once.

Example

Take an ordinary deploy job. It authenticates to a container registry, pulls the application image, and hands it to a cluster. The workflow below uses a GitHub-hosted runner, which is the default starting point for most repositories.

name: deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    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:${{ github.sha }}"
      - run: ./scripts/deploy.sh

Follow the bytes in the docker pull step. The image sits in a registry inside an AWS account in us-east-1. The machine running the step sits in a pool operated by GitHub, outside that account. The layers therefore leave the account across the internet path, and the account that owns the registry pays the internet data transfer rate on the volume.

Nothing in the workflow file says so. There is no step that mentions transfer, no output that reports gigabytes, and no line in the GitHub Actions run summary that names a cost. The only visible trace is a line on the AWS bill under a usage type such as DataTransfer-Out-Bytes.

The same pull, three placements

The step and the image are identical in all three rows below. The only variable is where the machine that runs the step lives.

Machine placementPath the layers takeMeter that counts it
Outside the account holding the registryRegistry to the internetInternet egress, per GB above the monthly allowance
Same account and Region, public subnetRegistry to compute in the RegionSame-Region transfer, $0.00 per GB on the ECR pricing page
Same account and Region, private subnet behind a managed NATRegistry to compute, through the NAT deviceNAT data processing per GB, plus the hourly device charge

What the volume works out to

Assume a 1.4 GB image and 0.2 GB of release artifacts pulled per deploy job, 40 deploy jobs per weekday, and 22 weekdays in the month. All rates below are the AWS list prices from the table above, checked on 2026-08-13.

Jobs per month: 40 times 22 equals 880.

Gigabytes per job: 1.4 plus 0.2 equals 1.6. Monthly volume: 880 times 1.6 equals 1,408 GB.

PlacementArithmeticMonthly transfer charge
Outside the account1,308 GB billable after the 100 GB allowance, times $0.09$117.72
Same account and Region, public subnet1,408 GB times $0.00$0.00
Same account and Region, private subnet with managed NAT1,408 GB times $0.045, plus 730 hours times $0.045$96.21

The billable figure in the first row credits the 100 GB monthly allowance once. An account whose production traffic already consumed the allowance pays the full 1,408 GB, which is $126.72.

Scale it with your own inputs. Doubling the image size doubles the line. Doubling deploy frequency doubles it again. A matrix that fans out to six legs, each pulling the same image, multiplies it by six unless the legs share a resolved digest and hit cached layers.

Placement is a label, not a rewrite

GitHub routes a job to a machine by matching the labels in runs-on, and the label is an opaque string. Moving the same steps onto a fleet that sits inside the account holding the registry is therefore a one-line change:

jobs:
  deploy:
    runs-on: warp-custom-deploy-use1

Runners registered in your own cloud account through WarpBuild use the Runner ID prefixed with warp-custom-, and the region and VPC come from the stack the runner belongs to, as described in the BYOC documentation. Labels for the hosted fleet follow a different shape, such as warp-ubuntu-latest-x64-4x, where the suffix names the operating system, the architecture, and the machine size.

The steps, the actions, and the image are unchanged in both cases. The row of the placement table that applies is what changed, and with it the meter that counts the pull.

FAQ

What are egress costs?

Charges a cloud provider applies to data that leaves its network, or that crosses a boundary inside it such as an availability zone or a region. They are metered per gigabyte moved, and they land on the cloud bill rather than on a source control or build service invoice.

Is inbound data charged as well?

Usually no. Most providers accept inbound data at no charge and meter the outbound direction. Managed NAT is the common exception, because the per-gigabyte data processing fee applies to traffic in both directions.

Why do egress costs show up in GitHub Actions?

Because jobs read large objects out of cloud stores. A deploy job pulls a container image from a registry and release artifacts from object storage on every run, so the gigabytes scale with merge frequency rather than with end user traffic.

How do I find egress charges on a cloud bill?

Group the bill by usage type, look for the data transfer and NAT data processing line items, then filter by the account, region, or tag that holds your runners. A curve that tracks merge volume rather than production traffic belongs to the pipeline.

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.