Start your free 14-day ContainIQ trial

How to Deploy Postgres on Kubernetes | Tutorial

In this step by step tutorial, readers learn how to deploy PostgreSQL on Kubernetes. Readers will also learn some best practices to implement.

March 13, 2023
Hrittik Roy
Software Engineer

Kubernetes is an open source system for managing containerized applications. The orchestrator provides a platform for automating deployment, scaling, and operating applications to facilitate easy management and reliable operation of services. Currently hosted by the CNCF, it’s one of the most popular orchestration systems available, and is used across various organizations in different industries.

Meanwhile, PostgreSQL has become a popular choice for organizations looking for a database management system that is both powerful and easy to use. Postgres is known for its reliability, flexibility, and performance, making it an excellent choice for mission-critical applications.

Though Kubernetes was originally developed to primarily support stateless applications, it’s grown to support stateful applications, as well, and PostgreSQL has been widely adopted inside enterprises as a database to maintain the state. In this article, you’ll walk through combining these popular technologies, Kubernetes and Postgres, with a step-by-step guide to deploying PostgreSQL on Kubernetes. You’ll also look at when you may want to deploy Postgres on Kubernetes, as well as some best practices for doing so.

Why PostgreSQL on Kubernetes?

The combination of PostgreSQL and Kubernetes provides a scalable and highly available (HA) database solution that’s well suited for modern application development and deployment practices. While creating a HA solution is out of the scope of this article, you’ll learn how to set up a simple container with PostgreSQL, which offers a number of benefits.

Improved Performance

Modern cloud-native applications are often built using microservices, which are small, self-contained services that can be individually deployed and scaled. PostgreSQL can be used as the database for each microservice, and Kubernetes can be used to manage the deployment and scaling of the application as a whole.

Easier Disaster Recovery

You don’t want to lose your operational or user data in any environment, but user error or technical failure may result in it anyhow. PostgreSQL’s Write-Ahead Logs (WAL) allows for easier disaster recovery by ensuring that all data is stored in the logs before the write operation to the database is performed, easing data recovery when required, and allowing even unwritten updates to be salvaged.

Better Utilization of Resources

Kubernetes is very efficient with scaling, and allows for use cases like scaling pods up during peak hours and down afterwards without service interruption. Scaling helps optimize resource utilization and save on cost, as you use only the resources necessary, not over provisioning to accommodate an infrequent or irregular surge in demand.

Deploying PostgreSQL on Kubernetes

To deploy PostgreSQL on Kubernetes, you need to have some tools set up.


  • A working Kubernetes cluster. For this tutorial, a DigitalOcean cluster is used, but the steps of this tutorial will be the same for any cluster. To work locally, you can use something like kind or minikubeto set your cluster.
  • A basic understanding of psql.
  • kubectl installed and authenticated on your environment. You’ll also need some working knowledge of the tool.

Deploying PostgreSQL

Deploying Postgres via ConfigMap with a PersistentVolume is one of the popular options for deployment, and it’s the approach you’ll be taking in this tutorial.

Apply ConfigMap

ConfigMaps help you separate data from code, and prevent secrets from exposing themselves in your application’s source code. With ConfigMaps, you can more easily deploy and update applications.

Create a ConfigMap by pasting the following code into your terminal:

cat <<EOF > postgres-config.yaml

apiVersion: v1
kind: ConfigMap
  name: postgres-config
    app: postgres
  POSTGRES_DB: postgresdb


The fields <terminal inline>POSTGRES_DB<terminal inline>, <terminal inline>POSTGRES_USER<terminal inline>, and <terminal inline>POSTGRES_PASSWORD<terminal inline> are your secrets, and you can change the values according to your preference. You can edit these values using text editors like vim or nano.

Apply Manifest

The command below creates a new ConfigMap for our PostgreSQL deployment with a custom configuration. The configuration consists of the fields <terminal inline>POSTGRES_DB<terminal inline>, <terminal inline>POSTGRES_USER<terminal inline>, and <terminal inline>POSTGRES_PASSWORD<terminal inline>.

@hrittikhere ➜ $ kubectl apply -f postgres-config.yaml 
configmap/postgres-config created

Check ConfigMap

Use the following command to verify that your configmap is present and ensure you can locate <terminal inline>postgres-config<terminal inline> on the terminal.

@hrittihere -> ~ $ kubectl get configmap

Name Data Age
kube-root-ca.crt 1 86m
postgress-config 3 3m42s

Create and Apply Persistent Storage Volume and Persistent Volume Claim

In order to ensure data persistence, you should use a persistent volume (PV) and persistent volume claims (PVC). A persistent volume (PV) is a durable volume that will remain even if the pod is deleted and stores data.

A persistent volume claim (PVC) is how users request and consume PV resources. Think of it as requesting the PV with parameters such as size of your storage disk, access modes, and storage class.

To deploy stateful applications such as a PostgreSQL database, for example, you’ll need to create a PVC for the database data. You can create a pod that mounts the PVC and runs the MySQL database.

For this tutorial, you will move forward with a local volume, using <terminal inline>/mnt/data<terminal inline> as the path to volume:

cat <<EOF > postgres-pvc-pv.yaml

kind: PersistentVolume
apiVersion: v1
  name: postgres-pv-volume  # Sets PV's name
    type: local  # Sets PV's type to local
    app: postgres
  storageClassName: manual
    storage: 5Gi # Sets PV Volume
    - ReadWriteMany
    path: "/mnt/data"
kind: PersistentVolumeClaim
apiVersion: v1
  name: postgres-pv-claim  # Sets name of PVC
    app: postgres
  storageClassName: manual
    - ReadWriteMany  # Sets read and write access
      storage: 5Gi  # Sets volume size

Apply Manifest

Run the following command to create a new PVC and PV for your PostgreSQL deployment:

@hrittikhere ➜ ~ $ kubectl apply -f postgres-pvc-pv.yaml 
persistentvolume/postgres-pv-volume created
persistentvolumeclaim/postgres-pv-claim created

Check PVC

Use the command below to check if PVC is bound to PV:

@hrittihere -> ~ $ kubectl get pvc

Name Status Volume Capacity Access Modes Storage Class Age
postgress-pv-claim Bound postgress-pv-volume 5Gi RWX manual 69s

If the <terminal inline>STATUS<terminal inline> is “Bound”, you can use it for your deployments.

Create and Apply PostgreSQL Deployment

Deployments are a way to manage rolling out and updating applications in a Kubernetes cluster. They provide a declarative way to define how an application should be deployed and updated, and can be used to roll back to previous versions if needed.

After creating PVCs, PVs, and ConfigMaps, you can create a stateful application by creating a stateful pod as follows:

cat <<EOF > postgres-deployment.yaml

apiVersion: apps/v1
kind: Deployment
  name: postgres  # Sets Deployment name
  replicas: 1
      app: postgres
        app: postgres
        - name: postgres
          image: postgres:10.1 # Sets Image
          imagePullPolicy: "IfNotPresent"
            - containerPort: 5432  # Exposes container port
            - configMapRef:
                name: postgres-config
            - mountPath: /var/lib/postgresql/data
              name: postgredb
        - name: postgredb
            claimName: postgres-pv-claim


Apply Manifest

The following command will create a new PostgreSQL deployment:

@hrittikhere ➜ ~ $ kubectl apply -f postgres-deployment.yaml 
deployment.apps/postgres created

Successful Creation

Use the following command to check if your deployments and the children objects, such as pods, are created successfully.

@hrittihere -> ~ $ kubectl get deployments

Name Ready Up-to-Date Available Age
postgress 0/1 1 0 21s

@hrittihere -> ~ $ kubectl get pods

Name Ready Status Restarts Age
postgress-7b9fb8d6c5-tnf68 1/1 Running 0 21s

Create and Apply PostgreSQL Service

Kubernetes services help you expose ports in various ways, including through a NodePort. NodePorts expose a service on every node in a cluster, meaning that the service is accessible from outside the cluster. This can be useful for services that need to be accessible from outside the cluster. To keep things simple for this tutorial, you’ll expose the database using NodePort with the help of the following manifest:

cat <<EOF > postgres-service.yaml

apiVersion: v1
kind: Service
  name: postgres # Sets service name
    app: postgres # Labels and Selectors
  type: NodePort # Sets service type
    - port: 5432 # Sets port to run the postgres application
    app: postgres


Apply Manifest

The command below will create a new PostgreSQL service which helps you to connect to <terminal inline>psql<terminal inline>:

@hrittikhere ➜ ~ $ kubectl apply -f postgres-service.yaml 
service/postgres created

List All Objects

Listing all the objects can be done using the following command:

@hrittihere -> ~ $ kubectl get all

Name Ready Status Restarts Age
pod/postgress-7b9fb8d6c5-tnf68 1/1 Running 0 70s
Name Type Cluster-Ip External-Ip Port(s) Age
service/kubernetes ClusterIP <NONE> 443/tcp 90m
service/postgress NodePort <NONE> 5432:31710/TCP 20s
Name Ready Up-to-Date Available Age
deployment.apps/postgress 1/1 1 1 74s
Name Desired Current Ready Age
replicaset.apps/postgress-7b9fb8d6c5 1 1 1 75s

Connect to PostgreSQL

The Kubernetes command line client ships with a feature that lets you connect to a pod directly from your host command line. The kubectl exec command accepts a pod name, any commands that should be executed, and an interactive flag that lets you launch a shell. You’ll use <terminal inline>kubectl exec<terminal inline> to connect to PostgreSQL pod:

kubectl exec -it [pod-name] --  psql -h localhost -U admin --password -p 5432 postgresdb

Use the password from the ConfigMap you created earlier, and the options <terminal inline>-it<terminal inline>.

  • -i: Stands for interactive.
  • -t: Attaches a tty (terminal) to the running command.

@hrittikhere ➜ ~ $ kubectl exec -it postgres-7b9fb8d6c5-tnf68 --  psql -h localhost -U admin --password -p 5432 postgresdb
Password for user admin: 
psql (10.1)
Type "help" for help.

postgresdb=# \l
                                 List of databases
    Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   
 postgres   | postgres | UTF8     | en_US.utf8 | en_US.utf8 | 
 postgresdb | postgres | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
            |          |          |            |            | postgres=CTc/postgres
 template1  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
            |          |          |            |            | postgres=CTc/postgres
(4 rows)


With PostgreSQL running, you’re now able to connect to the database and start writing some data to the tables.

Best Practices Deploying PostgreSQL on Kubernetes

When deploying PostgreSQL on Kubernetes, there are some best practices that you should follow to ensure the security and stability of your application.

Run the Container as Unprivileged User

You should always run the database container as an unprivileged user. This helps secure your data and avoid unauthorized access to your database. The most essential things to ensure that you run the container as an unprivileged user are:

*Make sure your container image launches as a user other than root (e.g. ensure USER is not 0 or root).

  • Make sure your Pod Security Context is set to non-root by setting runAsNonRoot to true.

Encrypt Your Data

You should always encrypt your data to avoid data loss or theft. You can use various tools and make sure your data is encrypted in transit as well as in rest to prevent various misconfiguration and breaches. To learn more about how to encrypt your data, check out this CNCF webinar on the subject.

Create a Separate Namespace for Your Database

You should create a separate namespace for your database so that it’s isolated from other applications and services. RBAC should be implemented to your namespace via ClusterRole and RoleBindings to prevent unauthorized access.

A new database namespace also helps monitor resources, and you can apply limits if you need to balance resources.

You can create a namespace as follows:

kubectl create namespace [namespace-name]

Final Thoughts

In this tutorial, you’ve deployed a PostgreSQL database running on Kubernetes. This setup is great for most use cases, but it’s important to remember you have configured it to store data in node-local memory. The official documentation provides more details about support for cloud volumes, NFS, cephs, and more.

Finally, keep an eye on your resource usage, and scale your deployment accordingly to avoid performance issues.

Start your free 14-day ContainIQ trial
Start Free TrialBook a Demo
No card required
Hrittik Roy
Software Engineer

Hrittik is a writer and a software engineer specializing in cloud native ecosystems. He has worked on many large-scale projects and has experience in both the technical and the business aspects of cloud computing. He is a frequent speaker at conferences and has written numerous articles on software development and distributed systems. In his free time, he likes to go for long walks.