Skip to main content

Tutorial

Get started on Google Cloud SQL for PostgreSQL

Follow this guided path to take a Google Cloud SQL for PostgreSQL instance from nothing to a first Elevarq Signals snapshot. It assumes you know Google Cloud but not PostgreSQL internals — the only database step is pasting a short block of SQL — and it ends with a working snapshot on your host.

Topology

Signals connects to Cloud SQL like any database client, so it has to sit on a network path that reaches your instance:

  • Recommended: reach the instance over its private IP from the same VPC (or a peered VPC), with Signals running on Compute Engine, GKE, or Cloud Run in that network.
  • From a laptop run the Cloud SQL Auth Proxy locally and connect Signals to 127.0.0.1 — the proxy brokers an authenticated, encrypted tunnel to the instance.
  • Alternatively, enable a public IP on the instance and add the Signals host to its Authorized networks.
  • Cloud SQL listens on 5432. The host you point Signals at (private IP, proxy address, or public IP) is not the database name — the database name is the logical DB inside the instance (often postgresor your application's database).

Prerequisites

  • A Cloud SQL PostgreSQL 14+ instance: the host (private IP, proxy address, or public IP), port (5432), database name, and a database user with a password.
  • Network reach from where Signals will run to that host — confirm with psql first (step 1).
  • docker (this guide) or the host binaries.
  • The instance's server-ca.pem, for verify-full TLS (downloaded in step 1).

1. Confirm you can reach the database

Download the instance's server CA from Connections → Security in the Cloud SQL console (see the Cloud SQL SSL/TLS docs), save it as signals-data/server-ca.pem, then connect over verify-full TLS. If this fails, fix networking or TLS before going further:

mkdir -p signals-data
# Download server-ca.pem from the Cloud SQL console
# (Connections -> Security) and save it as signals-data/server-ca.pem

psql "host=<private-ip-or-proxy> port=5432 dbname=<dbname> \
  user=<db-user> sslmode=verify-full \
  sslrootcert=signals-data/server-ca.pem"
Connecting through the Cloud SQL Auth Proxy? Point host=127.0.0.1 at the local proxy and keep sslmode=verify-full against the instance CA.

2. Create the read-only Signals role

Connect as a privileged user (the instance's built-in admin user, or any role with the grants below). Run this once per database, connected to the database you want to observe:

-- as a privileged user, connected to <dbname>:
CREATE ROLE signals LOGIN PASSWORD '<choose-a-strong-password>'
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
GRANT pg_monitor TO signals;
GRANT CONNECT ON DATABASE <dbname> TO signals;

Verify the role by connecting as signals in a fresh session, using the same host and TLS settings:

psql "host=<private-ip-or-proxy> port=5432 dbname=<dbname> \
  user=signals sslmode=verify-full \
  sslrootcert=signals-data/server-ca.pem"

Then, in that session:

SELECT count(*) FROM pg_stat_database;   -- succeeds: read access
CREATE TABLE _signals_check (id int);    -- fails: permission denied

3. (Optional) Enable query statistics

pg_stat_statements adds query-level statistics. It is optional — Signals collects everything else without it. On Cloud SQL you do not edit postgresql.conf; instead set the instance database flag:

  • Set the database flag cloudsql.enable_pg_stat_statements to on for the instance (see the Cloud SQL flags docs).
  • Then, connected to the database, run CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

4. Write the config and an API token

# API token shared by the daemon and signalsctl:
openssl rand -hex 32 > signals-data/api-token

cat > signals.yaml <<'YAML'
signals:
  poll_interval: 6h
  retention_days: 30
api:
  listen_addr: 0.0.0.0:8081   # bind inside the container; -p limits exposure to loopback
database:
  path: /data/signals.db
targets:
  - name: primary
    host: <private-ip-or-proxy>
    port: 5432
    dbname: <dbname>
    user: signals
    sslmode: verify-full
    sslrootcert_file: /data/server-ca.pem
    password_env: SIGNALS_PRIMARY_PASSWORD
YAML
The env-var password (password_env) keeps the first run simple. For production, store the secret in GCP Secret Manager (auth_method: secret_store), or go passwordless with Cloud SQL IAM (gcp_cloudsql_iam). See Authentication methods for all options.

5. Run the container

Bind-mount ./signals-data to /data so the SQLite store, the CA file, the token, and any exports all live in a directory you can see on the host:

docker run -d --name signals \
  -v "$PWD/signals.yaml:/etc/signals/signals.yaml:ro" \
  -v "$PWD/signals-data:/data" \
  -e SIGNALS_API_TOKEN="$(cat signals-data/api-token)" \
  -e SIGNALS_PRIMARY_PASSWORD="<the-password-from-step-2>" \
  -p 127.0.0.1:8081:8081 \
  ghcr.io/elevarq/signals:<version>
Which version? Replace <version> with a release tag from the Signals releases page and pin it (e.g. v1.2.0) for anything beyond a trial. ghcr.io/elevarq/signals:latest tracks the newest release and is fine for a quick evaluation.
The control API is bound to 0.0.0.0:8081 inside the container so the published port works, while -p 127.0.0.1:8081:8081restricts exposure to your host's loopback — reachable from this host and via docker exec, never from the network.

6. Verify, collect, export

Drive the running daemon with signalsctl inside the container — it inherits SIGNALS_API_TOKEN, so it authenticates to the local API automatically:

docker exec signals signalsctl status
docker exec signals signalsctl connect test primary
docker exec signals signalsctl collect now
docker exec signals signalsctl export --output /data/snapshot.zip

Because /data is the bind mount, the export lands at ./signals-data/snapshot.zip on the host (or pull it with docker cp signals:/data/snapshot.zip .). You now have a working snapshot.

Running docker exec signals signalsctl status shows the collector state per target; connect test primary confirms connectivity before you rely on the schedule.

Where next

This tutorial used a password for the simplest happy path. For passwordless Cloud SQL IAM authentication and day-to-day operations, see Authentication methods and Run as a service.