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 (oftenpostgresor 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
psqlfirst (step 1). docker(this guide) or the host binaries.- The instance's
server-ca.pem, forverify-fullTLS (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"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 denied3. (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_statementstoonfor 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
YAMLpassword_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><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.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.zipBecause /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.
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.