# [PostgreSQL Deployment Using Custom Helm Chart](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/postgresql-helm/)

## In this learning path

- [Introduction](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/)
- [Get started with Helm on Google Axion C4A (Arm-based)](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/background/)
- [Create a Google Axion C4A virtual machine on Google Cloud](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/instance/)
- [Install Helm](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/installation/)
- [Validate Helm workflows on a Google Axion C4A virtual machine](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/baseline/)
- [Prepare a GKE cluster for Helm deployments](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/gke-cluster-for-helm/)
- [PostgreSQL Deployment Using Custom Helm Chart](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/postgresql-helm/)
- [Deploy Redis on GKE](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/redis-helm/)
- [Deploy NGINX with public access](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/nginx-helm/)
- [Benchmark Helm concurrency on a Google Axion C4A virtual machine](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/benchmarking/)
- [Next Steps](https://learn.arm.com/learning-paths/servers-and-cloud-computing/helm-on-gcp/_next-steps/)

## Deploy PostgreSQL using a custom Helm chart
You’ll deploy PostgreSQL on Kubernetes using a custom Helm chart with persistent storage. After completing this section, PostgreSQL will be running with persistent storage using PVC, secure credentials using Kubernetes Secrets, and connectivity using psql.

### Prerequisites
Ensure Kubernetes and Helm are working:

```
kubectl get nodes
helm version
```
If these commands fail, fix them first before continuing.

### Create a working directory
Create a dedicated folder to store all Helm charts for microservices:

```
mkdir helm-microservices
cd helm-microservices
```

### Create a Helm chart
Create a Helm chart skeleton:

```
helm create my-postgres
```
**Directory structure:**

```
helm-microservices/
└── my-postgres/
    ├── Chart.yaml
    ├── values.yaml
    └── templates/
```

### Clean the chart
Remove unnecessary default files from `my-postgres/templates/`:

```
cd ./my-postgres/templates
rm -rf hpa.yaml ingress.yaml serviceaccount.yaml tests/ NOTES.txt httproute.yaml
cd $HOME/helm-microservices
```
Only PostgreSQL-specific templates will be maintained.

### Configure values.yaml
Replace the entire contents of `my-postgres/values.yaml` with the following to centralize all configurable settings including container image details, database credentials, and persistent storage configuration:

```
replicaCount: 1

image:
  repository: postgres
  tag: "15"
  pullPolicy: IfNotPresent

postgresql:
  username: admin
  password: admin123
  database: mydb

persistence:
  enabled: true
  size: 10Gi
  mountPath: /var/lib/postgresql
  dataSubPath: data
```
This configuration ensures consistent settings, avoids Helm template evaluation errors, and simplifies upgrades and maintenance.

### Create secret.yaml
Create `my-postgres/templates/secret.yaml` to store PostgreSQL credentials securely:

```
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "my-postgres.fullname" . }}
type: Opaque
stringData:
  POSTGRES_USER: {{ .Values.postgresql.username }}
  POSTGRES_PASSWORD: {{ .Values.postgresql.password }}
  POSTGRES_DB: {{ .Values.postgresql.database }}
```
This approach prevents hard-coding credentials and follows Kubernetes security best practices.

### Create pvc.yaml
Create `my-postgres/templates/pvc.yaml` with the following content to request persistent storage so PostgreSQL data remains available even if the pod restarts. Note the specification of the storage class that will be used **my-hyperdisk-sc** which was created and added to our cluster in the previous section. This hyperdisk-based storage class is required for the c4a architecture:

```
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ include "my-postgres.fullname" . }}-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: my-hyperdisk-sc
  resources:
    requests:
      storage: {{ .Values.persistence.size }}
```
Without a PVC, PostgreSQL data would be lost whenever the pod restarts.

### Deployment definition (deployment.yaml)
Replace the entire contents of `my-postgres/templates/deployment.yaml`:

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-postgres.fullname" . }}

spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ include "my-postgres.name" . }}

  template:
    metadata:
      labels:
        app: {{ include "my-postgres.name" . }}

    spec:
      containers:
        - name: postgres
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}

          ports:
            - containerPort: 5432

          envFrom:
            - secretRef:
                name: {{ include "my-postgres.fullname" . }}

          env:
            - name: PGDATA
              value: "{{ .Values.persistence.mountPath }}/{{ .Values.persistence.dataSubPath }}"

          volumeMounts:
            - name: postgres-data
              mountPath: {{ .Values.persistence.mountPath }}

      volumes:
        - name: postgres-data
          persistentVolumeClaim:
            claimName: {{ include "my-postgres.fullname" . }}-pvc
```
This configuration ensures PGDATA avoids the common lost+found directory issue, persistent storage is mounted safely, and secrets inject credentials at runtime.

### Service definition (service.yaml)
Replace the entire contents of `my-postgres/templates/service.yaml` to enable internal cluster communication:

```
apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-postgres.fullname" . }}
spec:
  type: ClusterIP
  ports:
    - port: 5432
      targetPort: 5432
  selector:
    app: {{ include "my-postgres.name" . }}
```
PostgreSQL should remain accessible only inside the Kubernetes cluster.

### Install PostgreSQL Using Helm
```
cd $HOME
cd helm-microservices
helm uninstall postgres-app || true
helm install postgres-app ./my-postgres
```
The output is similar to:

```
__output__
NAME: postgres-app
__output__
LAST DEPLOYED: Mon Jan 19 16:28:29 2026
__output__
NAMESPACE: default
__output__
STATUS: deployed
__output__
REVISION: 1
__output__
TEST SUITE: None
```

### Check the runtime status
Check the pod and PVC status:

```
kubectl get pods
kubectl get pvc
```
Check the **STATUS** of the pod first. If it’s not **Running**, wait 30 seconds and retry.

You should see an output similar to:

```
__output__ NAME                                        READY   STATUS    RESTARTS   AGE
__output__ postgres-app-my-postgres-6dbc8759b6-jgpxs   1/1     Running   0          40s
__output__
__output__ >kubectl get pvc
__output__ NAME                           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
__output__ postgres-app-my-postgres-pvc   Bound    pvc-5f3716df-39bb-4683-990a-c5cd3906fbce   10Gi       RWO            standard-rwo   <unset>                 33s
```

### Test PostgreSQL
Connect to PostgreSQL. First, get the pod name:

```
kubectl get pods
```
Then connect using the pod name from the output:

```
kubectl exec -it <postgres-pod-name> -- psql -U admin -d mydb
```
Replace `<postgres-pod-name>` with the actual pod name (for example, `postgres-app-my-postgres-6dbc8759b6-jgpxs`).

You should see an output similar to:

```
__output__ psql (15.15 (Debian 15.15-1.pgdg13+1))
__output__ Type "help" for help.
__output__
__output__ mydb=#
```
**Run test queries:**

```
CREATE TABLE test (id INT);
INSERT INTO test VALUES (1);
SELECT * FROM test;
```
You should see an output similar to:

```
__output__ mydb=# CREATE TABLE test (id INT);
__output__ INSERT INTO test VALUES (1);
__output__ SELECT * FROM test;
__output__ CREATE TABLE
__output__ INSERT 0 1
__output__  id
__output__ ----
__output__  1
__output__ (1 row)
```

## What you’ve accomplished and what’s next
You’ve successfully created a custom Helm chart and deployed PostgreSQL on Kubernetes with persistent storage, secure credentials using Secrets, and verified database functionality.

Next, you’ll deploy Redis on your GKE cluster using another custom Helm chart for internal communication within your Kubernetes environment.
