Guides / Private networking
Connect GitHub Actions to a private RDS database
Run an integration test against a private staging database from an EC2 runner in your VPC. Gondola launches the runner in the subnets and security groups you choose; your workflow selects it by label.
Start by connecting to an existing RDS PostgreSQL database and running SELECT 1. Once that works, replace the check with your application's integration tests.
Inside your AWS VPC
Controllers and runners still need outbound access to GitHub, AWS APIs and the registries your jobs use. Gondola uses your existing network; it does not create a NAT gateway or egress proxy. Check GitHub's communication requirements when configuring that access.
Before you start
- A Gondola installation, a private repository and an existing RDS PostgreSQL staging database with public access disabled. Use a dedicated database login with only the privileges needed for this check.
- Private runner subnets in the database's VPC and region, with working DNS, available addresses and outbound connectivity. This example assumes normal VPC routing without a network appliance between the runner and database.
- A GitHub runner group named
gondola-staging, restricted to the intended private repository. Configure astagingenvironment that permits your reviewed branch. Private-repository environments require an eligible GitHub plan. - An x64 runner image containing
psqland the current RDS CA bundle at/opt/gondola/rds-global-bundle.pem. Gondola's standard images do not includepsql; follow the runner image guide to prepare your own and pin its digest. The EC2 runner must be able to pull it; private registries need runner-side authentication and do not inherit the controller's registry credentials.
Anyone who can run code on this fleet can use its network access. Keep it separate from public or untrusted pull-request jobs. Terminating the instance after a job does not limit what that job can do while it runs.
1. Allow the database connection
Create a dedicated runner security group. Allow outbound PostgreSQL to the database group, and allow the database to receive PostgreSQL from the runner group. These references work as runner instances come and go; you do not need a list of their IP addresses. See AWS security-group references.
Add this to your Terraform or OpenTofu configuration alongside the Gondola module. Set var.vpc_id to the staging VPC and var.staging_database_security_group_id to a group already attached to the database.
resource "aws_security_group" "staging_runner" {
name_prefix = "gondola-staging-runner-"
description = "Runner access for staging database checks"
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_egress_rule" "runner_postgres" {
security_group_id = aws_security_group.staging_runner.id
referenced_security_group_id = var.staging_database_security_group_id
ip_protocol = "tcp"
from_port = 5432
to_port = 5432
}
resource "aws_vpc_security_group_ingress_rule" "database_from_runner" {
security_group_id = var.staging_database_security_group_id
referenced_security_group_id = aws_security_group.staging_runner.id
ip_protocol = "tcp"
from_port = 5432
to_port = 5432
}
resource "aws_vpc_security_group_egress_rule" "runner_https" {
security_group_id = aws_security_group.staging_runner.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "tcp"
from_port = 443
to_port = 443
}The last rule allows HTTPS to any IPv4 address. Use your existing firewall policy if you need to restrict destinations further. Other workflow dependencies may need additional access.
This connection needs no inbound runner rule. Check every security group attached to both resources: their permissions combine. If another stack or inline Terraform rules already manage the database's rules, make the equivalent change there instead of managing a rule twice.
2. Assign the network to a runner fleet
Add this entry to the fleets map in your Gondola module block, keeping existing entries. Set var.staging_runner_subnet_ids to the private runner subnets and var.approved_staging_runner_image to the full image reference, pinned by digest, prepared above.
fleets = {
staging = {
scale_set_name = "gondola-staging"
runner_group = "gondola-staging"
architecture = "x64"
capacity_mode = "on-demand"
vpc_id = var.vpc_id
subnet_ids = var.staging_runner_subnet_ids
security_group_ids = [aws_security_group.staging_runner.id]
runner_container_image = var.approved_staging_runner_image
instance_type = "m7i.large"
policy_arns = []
min_runners = 0
max_runners = 2
max_runner_lifetime = "30m"
}
}This example adds no AWS policies to the generated runner role. A password-authenticated PostgreSQL connection does not need AWS API permissions. If your job also calls AWS services, use a job-specific role through GitHub OIDC.
Keep the default two controllers across two Availability Zones; their subnets can differ from the runner subnets. When you supply runner security groups, you manage their rules. Gondola does not add its default egress rules to them.
Review the plan for resource replacements and runner-label changes before applying, especially when moving from older single-fleet settings to a fleet map. The fleet configuration guide describes the available fields.
3. Check the connection from GitHub Actions
Add these values to the repository's staging environment:
| Name | Type | Value |
|---|---|---|
STAGING_DB_HOST | Variable | The RDS endpoint hostname |
STAGING_DB_NAME | Variable | The staging database name |
STAGING_DB_USER | Variable | The dedicated database login |
STAGING_DB_PASSWORD | Secret | That login's password |
Save this workflow on your reviewed main branch. If you use another branch name, update the branch check and environment policy together.
name: Check staging database
on:
workflow_dispatch:
permissions: {}
jobs:
database-check:
if: github.ref == 'refs/heads/main'
runs-on: gondola-staging
environment: staging
timeout-minutes: 10
steps:
- name: Check the private database connection
env:
PGHOST: ${{ vars.STAGING_DB_HOST }}
PGPORT: "5432"
PGDATABASE: ${{ vars.STAGING_DB_NAME }}
PGUSER: ${{ vars.STAGING_DB_USER }}
PGPASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
PGSSLMODE: verify-full
PGSSLROOTCERT: /opt/gondola/rds-global-bundle.pem
PGCONNECT_TIMEOUT: "10"
PGOPTIONS: "-c default_transaction_read_only=on"
run: |
psql --no-psqlrc --no-password --set=ON_ERROR_STOP=1 \
--command='SELECT 1;'verify-full checks the database certificate and hostname against the RDS CA bundle. Use the actual RDS endpoint and keep the bundle in your image current. See PostgreSQL certificate verification on RDS.
Run the workflow manually. A successful query returns 1. Confirm the job's EC2 instance terminates afterward. When adding integration tests, grant only the database privileges they need and use staging data. Tests that need writes must also remove the read-only PGOPTIONS setting above. Keep credentials and sensitive query results out of logs.
If the check fails
| Symptom | Check |
|---|---|
| Job stays queued | The fleet label, runner-group repository access and fleet readiness. |
| Runner fails before the job starts | Outbound connectivity, image-pull access and the image digest. |
| Hostname does not resolve | The database endpoint and VPC DNS configuration. |
| Connection times out | Runner subnets, routes, network ACLs and security-group rules. |
| Certificate verification fails | The RDS endpoint hostname and current CA bundle. |
| Authentication fails | The environment secret, database name and login privileges. |
Include networking in your costs
min_runners = 0 avoids keeping idle runners. Controllers and network services can still incur charges between jobs. Account for EC2, EBS, controllers, database usage, logs and networking. NAT gateways have hourly and data-processing charges; cross-AZ traffic and interface endpoints can add costs too.
Use the cost calculator with your own workload and network estimate. If you haven't installed Gondola yet, start with one workflow. You can email us if you get stuck.