Spacefleet

Database configuration

Spacefleet stores all of its data in PostgreSQL. You can run the bundled Postgres for a trial, or — recommended for production — point the app at a managed database such as Amazon RDS, Google Cloud SQL, or Azure Database for PostgreSQL.

This page covers how to configure that connection: the connection string, how to require encrypted connections, and how to enforce full TLS certificate validation (including providing a custom CA bundle when your provider needs one).

For where this fits in a full install, see Install & Configure with Helm.

How the database is configured

The app reads a single Postgres connection string from the DATABASE_URL environment variable. The Helm chart gives you three ways to supply it:

ModeHelm valuesWhen to use
Bundled (default)postgresql.enabled=trueTrials and small deployments. Single-replica, not HA.
External, inline URLpostgresql.enabled=false, externalDatabase.url=…Quick external setup; the URL ends up in the release's Secret.
External, existing Secretpostgresql.enabled=false, externalDatabase.existingSecret=…Recommended for production — credentials stay in a Secret you control.

For production, disable the bundled Postgres and point at your managed database. Keeping the connection string in a Secret you create yourself keeps credentials out of your Helm values and release history:

kubectl create secret generic spacefleet-db \
  --from-literal=DATABASE_URL='postgres://user:pass@mydb.example.com:5432/spacefleet?sslmode=verify-full'
# values.prod.yaml
postgresql:
  enabled: false
externalDatabase:
  existingSecret: spacefleet-db
  # existingSecretKey defaults to DATABASE_URL; override if your Secret uses a
  # different key.

The same Secret is consumed by the web, worker, and migrate pods — all three connect to Postgres with this one connection string.

The connection string

DATABASE_URL is a standard PostgreSQL connection URI:

postgres://USER:PASSWORD@HOST:PORT/DATABASE?PARAM=VALUE&…

TLS behavior is controlled entirely by query parameters on this URL. The ones that matter for a secure connection are:

ParameterPurpose
sslmodeHow strictly TLS is required and validated (see below).
sslrootcertPath to a CA bundle (PEM) used to verify the server certificate. If omitted, the system trust store is used.
sslcert / sslkeyClient certificate and key, for databases that require mutual TLS (mTLS).

Always URL-encode special characters in the username and password (for example @ becomes %40), otherwise they will be misparsed as part of the host.

TLS modes

The sslmode parameter has the standard libpq meanings. The important distinction is whether the certificate is actually validated:

sslmodeEncrypted?Validates cert chain?Validates hostname?
disableNo
requireYesNoNo
verify-caYesYesNo
verify-fullYesYesYes

A common misconception is that sslmode=require is secure. It encrypts the connection but performs no certificate validation at all, so it does not protect against an active man-in-the-middle. For any database reachable over a network you do not fully trust, use verify-full — it verifies both that the certificate chains to a trusted CA and that it was issued for the host you are connecting to.

postgres://user:pass@mydb.example.com:5432/spacefleet?sslmode=verify-full

Full validation against publicly-trusted CAs

With sslmode=verify-full and no sslrootcert, the app validates the server certificate against the operating system's trust store. The Spacefleet container image ships the standard CA-certificates bundle, so this works out of the box for any database whose certificate chains to a public root.

Amazon RDS is the common case here. Modern RDS instances (using the default rds-ca-rsa2048-g1 certificate authority) present a certificate that chains to publicly-trusted Amazon roots, which are already in the image's trust store. So for a current RDS instance you usually need no certificate file at all — just require full validation:

externalDatabase:
  url: "postgres://user:pass@mydb.abc123.us-east-1.rds.amazonaws.com:5432/spacefleet?sslmode=verify-full"

Use the explicit-CA approach in the next section if any of the following apply:

  • You are on an older RDS CA (e.g. the legacy rds-ca-2019) whose root is not in public trust stores.
  • Your security policy requires pinning to your provider's published CA rather than trusting public roots.
  • Your provider uses a private CA (common with on-prem or internal managed databases).

Providing a custom CA bundle (e.g. Amazon RDS)

To validate against a specific CA bundle, you must do two things:

  1. Make the CA bundle (a PEM file) available inside the app's pods.
  2. Point sslrootcert at the path you mounted it to.

The chart exposes config.extraVolumes and config.extraVolumeMounts for exactly this. They are applied to all three database clients — the web, worker, and migrate pods — so migrations validate the same way the running app does.

Step 1 — get the CA bundle into the cluster

Download your provider's CA bundle and store it in a ConfigMap (a CA certificate is public, not a secret, so a ConfigMap is appropriate). For Amazon RDS, AWS publishes a global bundle covering all regions:

curl -fsSLo global-bundle.pem \
  https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem

kubectl create configmap rds-ca-bundle \
  --from-file=global-bundle.pem=global-bundle.pem

Create the ConfigMap in the same namespace as the Spacefleet release.

Step 2 — mount it and point sslrootcert at it

# values.prod.yaml
postgresql:
  enabled: false

externalDatabase:
  # The mount path below is referenced by sslrootcert.
  url: "postgres://user:pass@mydb.abc123.us-east-1.rds.amazonaws.com:5432/spacefleet?sslmode=verify-full&sslrootcert=/etc/ssl/rds/global-bundle.pem"

config:
  extraVolumes:
    - name: rds-ca
      configMap:
        name: rds-ca-bundle
  extraVolumeMounts:
    - name: rds-ca
      mountPath: /etc/ssl/rds
      readOnly: true

The file lands at /etc/ssl/rds/global-bundle.pem (the mountPath plus the key you used in the ConfigMap), which is what sslrootcert references.

If you keep the connection string in an existing Secret rather than externalDatabase.url, put the same sslmode/sslrootcert query parameters on the DATABASE_URL value inside that Secret — the volume configuration in config.* is independent of how the URL is supplied.

Mutual TLS (client certificates)

If your database requires the client to present a certificate, mount the client certificate and key the same way (typically from a Secret rather than a ConfigMap, since the key is sensitive) and add sslcert and sslkey to the connection string:

…?sslmode=verify-full&sslrootcert=/etc/ssl/rds/global-bundle.pem&sslcert=/etc/ssl/db-client/tls.crt&sslkey=/etc/ssl/db-client/tls.key

Verifying the connection

After helm upgrade, the migrate Job runs first. If TLS is misconfigured it fails there, before the app rolls out. Check it:

kubectl logs job/spacefleet-migrate

Common failures and what they mean:

Log messageCause
x509: certificate signed by unknown authorityThe CA that signed the server cert is not trusted — wrong or missing sslrootcert, or the bundle does not cover this server.
certificate is valid for X, not YHostname mismatch under verify-full — connect using the hostname the certificate was issued for, not an IP or alias.
no such file or directory referencing your sslrootcert pathThe CA file is not mounted where the URL expects it — check that mountPath + the ConfigMap key equal the sslrootcert path.
connection refused / timeoutsNot TLS — a network/security-group/firewall issue reaching the database host.

Once migrations succeed, the web and worker pods use the identical connection string and TLS assets, so a green migrate Job means the app will connect too.