Skip to content

Running the Processor

The Processor binary is a single, statically compiled Go program with no external dependencies. It can run on any Linux VM (virtual machine), in a container, or under any process supervisor. The sections below cover the recommended approach for each platform.

Tip

If you already have an application server or VM for this environment, running the Processor there as a systemd service is the simplest option, and costs nothing extra.

If you need dedicated infrastructure, this table summarises the recommended options:

Platform Recommended option Approximate monthly cost
AWS ECS Fargate (0.25 vCPU / 512 MB) ~USD 7–10
Azure Azure Container Instances ~USD 5–8
Google Cloud Compute Engine e2-micro ~USD 7 (free tier eligible)
Any systemd on any existing VM No additional cost

On a VM — systemd (all platforms)

This approach works on any Linux VM, and is identical regardless of which cloud provider you use.

Download and install the binary:

# AMD64 (most cloud VMs)
curl -LO https://gitlab.com/pebble-it/pg_relay/-/releases/latest/downloads/pg_relay-linux-x86_64
chmod +x pg_relay-linux-x86_64
sudo mv pg_relay-linux-x86_64 /usr/local/bin/pg_relay

# ARM64 (AWS Graviton, Ampere Altra, etc.)
curl -LO https://gitlab.com/pebble-it/pg_relay/-/releases/latest/downloads/pg_relay-linux-arm64
chmod +x pg_relay-linux-arm64
sudo mv pg_relay-linux-arm64 /usr/local/bin/pg_relay

Create the configuration directory and files:

sudo mkdir -p /etc/pg_relay

Create /etc/pg_relay/pg_relay.env (replace the values for your own environment):

PGHOST=your-db-host
PGPORT=5432
PGDATABASE=your_database
PGUSER=pgrelay
PGPASSFILE=/etc/pg_relay/.pgpass
PGSSLMODE=require
sudo chmod 600 /etc/pg_relay/pg_relay.env

Create /etc/pg_relay/.pgpass:

your-db-host:5432:your_database:pgrelay:choose-a-strong-password
sudo chmod 600 /etc/pg_relay/.pgpass

Create the systemd unit file at /etc/systemd/system/pg_relay.service:

[Unit]
Description=pg_relay Processor
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/pg_relay/pg_relay.env
ExecStart=/usr/local/bin/pg_relay
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now pg_relay
sudo journalctl -u pg_relay -f

The service restarts automatically if the Processor ever exits, including after a transient connection failure.


Building a container image (AWS and Azure)

Both ECS Fargate and Azure Container Instances use the same container image. Build it once and push it to your cloud provider's registry.

Dockerfile:

FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY pg_relay-linux-x86_64 /usr/local/bin/pg_relay
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /usr/local/bin/pg_relay /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh:

The Processor requires a PGPASSFILE. This script creates one at container startup, from a secret value injected by the container runtime (ECS Secrets, or ACI secure environment variables). The secret variable here is named DB_PASSWORD — deliberately distinct from PostgreSQL's own PGPASSWORD variable — and is only used by this script to write the file:

#!/bin/sh
set -e
mkdir -p /run/secrets
printf '%s:%s:%s:%s:%s\n' \
    "$PGHOST" "$PGPORT" "$PGDATABASE" "$PGUSER" "$DB_PASSWORD" \
    > /run/secrets/.pgpass
chmod 600 /run/secrets/.pgpass
export PGPASSFILE=/run/secrets/.pgpass
unset DB_PASSWORD
exec /usr/local/bin/pg_relay "$@"

Place pg_relay-linux-x86_64, Dockerfile, and entrypoint.sh in the same directory before building.


On AWS — ECS Fargate

ECS (Elastic Container Service) Fargate runs containers without you having to manage any underlying servers.

Push the image to Amazon ECR (Elastic Container Registry):

aws ecr create-repository --repository-name pg-relay

aws ecr get-login-password --region your-region | docker login \
    --username AWS \
    --password-stdin \
    <account-id>.dkr.ecr.your-region.amazonaws.com

docker build -t pg-relay .
docker tag pg-relay:latest \
    <account-id>.dkr.ecr.your-region.amazonaws.com/pg-relay:latest
docker push \
    <account-id>.dkr.ecr.your-region.amazonaws.com/pg-relay:latest

Store the database password in AWS Secrets Manager:

aws secretsmanager create-secret \
    --name pg-relay/db-password \
    --secret-string 'choose-a-strong-password'

Note the secret ARN (Amazon Resource Name — AWS's unique identifier format for cloud resources) returned here — you need it in the task definition below.

Create the CloudWatch log group:

aws logs create-log-group --log-group-name /ecs/pg-relay

ECS Task Definition — save as pg-relay-task.json and replace every placeholder value:

{
  "family": "pg-relay",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::<account-id>:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "pg-relay",
      "image": "<account-id>.dkr.ecr.your-region.amazonaws.com/pg-relay:latest",
      "essential": true,
      "environment": [
        { "name": "PGHOST",     "value": "your-db-endpoint.region.rds.amazonaws.com" },
        { "name": "PGPORT",     "value": "5432" },
        { "name": "PGDATABASE", "value": "your_database" },
        { "name": "PGUSER",     "value": "pgrelay" },
        { "name": "PGSSLMODE",  "value": "require" }
      ],
      "secrets": [
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:your-region:<account-id>:secret:pg-relay/db-password"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/pg-relay",
          "awslogs-region": "your-region",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

The ecsTaskExecutionRole IAM role needs secretsmanager:GetSecretValue permission on the secret's ARN, in addition to the standard ECS execution role policies (AmazonECSTaskExecutionRolePolicy).

Register the task definition and create the ECS service:

aws ecs register-task-definition --cli-input-json file://pg-relay-task.json

aws ecs create-service \
    --cluster your-cluster \
    --service-name pg-relay \
    --task-definition pg-relay \
    --desired-count 1 \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={
        subnets=[subnet-your-subnet],
        securityGroups=[sg-your-sg],
        assignPublicIp=DISABLED
    }"

Place the task in the same VPC (Virtual Private Cloud) as your RDS or Aurora instance. Its security group must allow outbound TCP traffic to the database's security group on port 5432. The ECS service keeps one task running and restarts it automatically if it exits.

Follow the logs:

aws logs tail /ecs/pg-relay --follow

On Azure — Azure Container Instances

Push the image to Azure Container Registry:

az acr create \
    --resource-group your-resource-group \
    --name yourregistryname \
    --sku Basic

az acr login --name yourregistryname

docker build -t pg-relay .
docker tag pg-relay:latest yourregistryname.azurecr.io/pg-relay:latest
docker push yourregistryname.azurecr.io/pg-relay:latest

Retrieve ACR credentials for ACI:

ACR_USER=$(az acr credential show --name yourregistryname --query username -o tsv)
ACR_PASS=$(az acr credential show --name yourregistryname --query passwords[0].value -o tsv)

Store the database password in Azure Key Vault (or retrieve it if it is already stored there):

az keyvault create \
    --name your-kv-name \
    --resource-group your-resource-group \
    --location your-location

az keyvault secret set \
    --vault-name your-kv-name \
    --name pg-relay-db-password \
    --value 'choose-a-strong-password'

DB_PASS=$(az keyvault secret show \
    --vault-name your-kv-name \
    --name pg-relay-db-password \
    --query value -o tsv)

Create the ACI container:

az container create \
    --resource-group your-resource-group \
    --name pg-relay \
    --image yourregistryname.azurecr.io/pg-relay:latest \
    --cpu 0.25 \
    --memory 0.25 \
    --restart-policy Always \
    --registry-login-server yourregistryname.azurecr.io \
    --registry-username "$ACR_USER" \
    --registry-password "$ACR_PASS" \
    --environment-variables \
        PGHOST=your-server.postgres.database.azure.com \
        PGPORT=5432 \
        PGDATABASE=your_database \
        PGUSER=pgrelay \
        PGSSLMODE=require \
    --secure-environment-variables \
        DB_PASSWORD="$DB_PASS"

--secure-environment-variables values are encrypted and never appear in portal output or CLI responses. The container restarts automatically because of --restart-policy Always.

Follow the logs:

az container logs --resource-group your-resource-group --name pg-relay --follow

On Google Cloud — Compute Engine

A Compute Engine e2-micro instance is within the always-free tier (one instance per region), and is the most straightforward way to run the Processor on GCP. The Cloud SQL Auth Proxy and the Processor run together as separate systemd services on the same VM.

Create the VM:

gcloud compute instances create pg-relay-Processor \
    --machine-type=e2-micro \
    --image-family=debian-12 \
    --image-project=debian-cloud \
    --zone=your-zone \
    --scopes=cloud-platform

--scopes=cloud-platform grants the VM's default service account access to Cloud SQL via the proxy. For production, create a dedicated service account with only the Cloud SQL Client role, and use --service-account alongside --scopes=cloud-platform with that account instead.

SSH into the VM:

gcloud compute ssh pg-relay-Processor --zone=your-zone

Install the binary and the proxy:

# pg_relay binary
curl -LO https://gitlab.com/pebble-it/pg_relay/-/releases/latest/downloads/pg_relay-linux-x86_64
chmod +x pg_relay-linux-x86_64
sudo mv pg_relay-linux-x86_64 /usr/local/bin/pg_relay

# Cloud SQL Auth Proxy
curl -o cloud-sql-proxy \
  https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.0/cloud-sql-proxy.linux.amd64
chmod +x cloud-sql-proxy
sudo mv cloud-sql-proxy /usr/local/bin/cloud-sql-proxy

Create the proxy's systemd unit at /etc/systemd/system/cloud-sql-proxy.service:

Replace your-project:your-region:your-instance with your Cloud SQL instance's connection name:

[Unit]
Description=Cloud SQL Auth Proxy
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/cloud-sql-proxy \
    --port 5432 \
    your-project:your-region:your-instance
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Create the Processor's configuration:

sudo mkdir -p /etc/pg_relay

# .pgpass uses 127.0.0.1 — the proxy's local address
sudo tee /etc/pg_relay/.pgpass > /dev/null <<'EOF'
127.0.0.1:5432:your_database:pgrelay:choose-a-strong-password
EOF
sudo chmod 600 /etc/pg_relay/.pgpass

sudo tee /etc/pg_relay/pg_relay.env > /dev/null <<'EOF'
PGHOST=127.0.0.1
PGPORT=5432
PGDATABASE=your_database
PGUSER=pgrelay
PGPASSFILE=/etc/pg_relay/.pgpass
PGSSLMODE=disable
EOF
sudo chmod 600 /etc/pg_relay/pg_relay.env

PGSSLMODE=disable only applies to the connection between the Processor and the local proxy. The proxy's own connection to Cloud SQL is always encrypted, regardless.

Create the Processor's systemd unit at /etc/systemd/system/pg_relay.service:

The Requires directive makes sure the proxy is running before the Processor starts:

[Unit]
Description=pg_relay Processor
After=cloud-sql-proxy.service
Requires=cloud-sql-proxy.service

[Service]
Type=simple
EnvironmentFile=/etc/pg_relay/pg_relay.env
ExecStart=/usr/local/bin/pg_relay
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Enable and start both services:

sudo systemctl daemon-reload
sudo systemctl enable --now cloud-sql-proxy
sudo systemctl enable --now pg_relay
sudo journalctl -u pg_relay -f

Continue to Upgrading for how to move a cloud deployment to a new pg_relay release.