Skip to content

How to Create a Postgres Database#

Learn how to provision a PostgreSQL database in your project.

Jordan, a backend engineer at ACME Corp, needs a managed PostgreSQL database for her application. She can provision one, scale it for high availability, and connect to it from a workload in minutes.

Prerequisites#

  • A Project already created
  • The postgres.database.cloud.stakater.com API available
  • kubectl configured with your project kubeconfig
  • To read the credentials: access to your organisation's Vault from a device enrolled on the Mesh

What Gets Created#

When you create a Postgres claim, the platform provisions:

  • A PostgreSQL database sized to your requested number of instances and disk
  • Credentials in your organisation's Vault, at the path published in the claim's status
  • Optionally, an externally-reachable endpoint when exposeLoadBalancer: true

Step 1: Define a Postgres Claim#

Create a file named db.yaml:

apiVersion: database.cloud.stakater.com/v1
kind: Postgres
metadata:
  name: my-db
spec:
  parameters: {}

Optional Parameters#

Parameter Default Description
parameters.instances 1 Number of PostgreSQL instances (replicas). Use 3 for HA.
parameters.storage.size 10Gi Disk size per instance
parameters.exposeLoadBalancer false When true, the database is reachable from outside the project

Step 2: Apply the Claim#

kubectl apply -f db.yaml

Step 3: Verify the Database#

Check the claim status:

kubectl get postgres my-db

Wait for it to become ready:

NAME    SYNCED   READY   AGE
my-db   True     True    2m

Inspect the endpoint metadata and the credentials location:

kubectl get postgres my-db -o jsonpath='{.status.connection}'
{"host":"my-db-rw.projects.svc.cluster.local","port":"5432","database":"my-db","credentialsRef":{"vault":"https://bao.acme.mesh.example.stakater.cloud","mount":"services","path":"my-project/postgres/my-db"}}

Step 4: Read the Credentials#

The platform stores the credentials in your organisation's Vault at status.connection.credentialsRef. Open the Vault URL in a browser (from a Mesh-enrolled device) and navigate to the services secret engine, or use the CLI:

export BAO_ADDR=$(kubectl get postgres my-db -o jsonpath='{.status.connection.credentialsRef.vault}')
bao login -method=oidc
bao kv get -mount=services $(kubectl get postgres my-db -o jsonpath='{.status.connection.credentialsRef.path}')

Keys cover the common client conventions:

Key Description
username / user Application user (both keys carry the same value)
password Password for the application user
host Read-write service hostname
port Service port
dbname Database name
uri PostgreSQL connection URI
jdbc-uri JDBC URL
fqdn-uri, fqdn-jdbc-uri URI / JDBC URL with the host's fully-qualified cluster DNS name
pgpass A line for ~/.pgpass

Mount the credentials into a workload#

Create a Secret next to your workload from the Vault values, then reference it with envFrom:

bao kv get -mount=services -format=json my-project/postgres/my-db \
  | jq -r '.data.data' > creds.json
kubectl create secret generic my-db --from-env-file=<(jq -r 'to_entries[] | "\(.key)=\(.value)"' creds.json)
rm creds.json
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:latest
          envFrom:
            - secretRef:
                name: my-db

The container will see DATABASE_URL-equivalent values through the standard keys (host, port, username, password, dbname, uri).

Test from your workstation#

For ad-hoc testing — psql from your laptop, a local migration runner, a BI tool — set parameters.exposeLoadBalancer: true on the claim. The platform provisions an externally-routable endpoint and surfaces its hostname / IP under status.connection.externalHost:

apiVersion: database.cloud.stakater.com/v1
kind: Postgres
metadata:
  name: my-db
spec:
  parameters:
    exposeLoadBalancer: true

Port 5432 may not be open to the internet

Whether externalHost is reachable from the internet depends on your cloud's network perimeter. On Stakater's production cloud the perimeter admits only a few ports, such as 443 for HTTPS, and PostgreSQL's port 5432 is not one of them: the claim still reports Ready and externalHost still gets an address, but connections from outside the platform's network time out. Ask your platform team which ports your perimeter admits.

From a network that can reach the endpoint, connect using the credentials from the Vault:

PG_HOST=$(kubectl get postgres my-db -o jsonpath='{.status.connection.externalHost}')
CREDS=$(bao kv get -mount=services -format=json my-project/postgres/my-db | jq -r '.data.data')
export PGUSER=$(echo "$CREDS" | jq -r '.username')
export PGPASSWORD=$(echo "$CREDS" | jq -r '.password')
export PGDATABASE=$(echo "$CREDS" | jq -r '.dbname')

psql -h "$PG_HOST" -p 5432 -c "SELECT version();"

If you left exposeLoadBalancer off, the database is only reachable from inside the project — a one-off connection from your laptop is still possible by port-forwarding:

kubectl port-forward svc/my-db-rw 5432:5432 &
psql -h 127.0.0.1 -p 5432 -c "SELECT version();"

Step 5: Scale the Database (Optional)#

High availability with 3 instances#

apiVersion: database.cloud.stakater.com/v1
kind: Postgres
metadata:
  name: app-db
spec:
  parameters:
    instances: 3
    storage:
      size: 50Gi

Step 6: Delete the Database#

kubectl delete postgres my-db

The platform tears down the database and removes the credentials from your organisation's Vault. If you copied the credentials into Secrets next to your workloads, remember to delete those too:

kubectl delete secret my-db

Warning

All data is deleted when the claim is removed. Back up any database you need to keep before deletion.

What's Next?#