Lab Goal
In this lab, you will deploy a PostgreSQL database on OpenShift Local using the OpenShift web console. You will create the database using modern OpenShift resources such as Deployment, Service, Secret, ConfigMap, and PersistentVolumeClaim.
This lab replaces the older MongoDB web-console lab that used an outdated MongoDB image, an ephemeral database template, hard-coded credentials, and DeploymentConfig. The new lab uses current OpenShift-friendly practices.
By the end of this lab, you will understand how an application or client pod connects to a database inside OpenShift using internal service discovery.
What You Will Learn
You will learn how to:
- Start OpenShift Local.
- Log in to the OpenShift web console.
- Create a new project.
- Create a PostgreSQL database using the web console.
- Store database credentials in a Secret.
- Store non-sensitive database configuration in a ConfigMap.
- Create persistent storage using a PersistentVolumeClaim.
- Expose the database internally using a Service.
- Deploy a database client pod.
- Connect to PostgreSQL from inside the OpenShift cluster.
- Create a table and insert test data.
- Verify that data survives a database pod restart.
- Validate everything using optional
occommands. - Clean up the lab environment.
Lab Architecture
In this lab, you will create the following OpenShift resources:
| Resource | Name | Purpose |
|---|---|---|
| Project | lab9-db-web-console | Isolated namespace for this lab |
| Secret | postgresql-secret | Stores database username, password, and database name |
| ConfigMap | postgresql-config | Stores non-sensitive PostgreSQL settings |
| PersistentVolumeClaim | postgresql-data | Provides persistent database storage |
| Deployment | postgresql | Runs the PostgreSQL database pod |
| Service | postgresql | Provides stable internal DNS name for the database |
| Deployment | db-client | Runs a client pod used to test database connectivity |
Why This Lab Uses PostgreSQL Instead of the Old MongoDB Template
The older lab used an old MongoDB 3.6 template and an ephemeral database. That approach is not ideal for a current OpenShift student lab.
This replacement lab uses PostgreSQL because:
- PostgreSQL is commonly used in enterprise application environments.
- The database runs as a standard Kubernetes Deployment.
- Credentials are stored in a Secret.
- Non-sensitive settings are stored in a ConfigMap.
- Data is stored on a PersistentVolumeClaim.
- Students can clearly understand how applications connect to databases through internal Services.
Important Concept
A database should usually not be exposed publicly through an OpenShift Route.
In OpenShift:
| Resource | Used For |
|---|---|
| Service | Internal communication inside the cluster |
| Route | External HTTP/HTTPS access to web applications |
In this lab, PostgreSQL will only be available inside the OpenShift cluster through the internal service name:
postgresql
The database will not be exposed using a Route.
Part 1: Prerequisites
Required Tools
You need the following tools installed on your machine:
- OpenShift Local
crccommandoccommand- A browser
- At least 4 CPUs and enough memory available for OpenShift Local
Recommended OpenShift Local Configuration
OpenShift Local with the OpenShift preset requires a local single-node OpenShift cluster. For smooth lab work, use at least:
| Resource | Recommended |
|---|---|
| CPU | 4 or more |
| Memory | 12 GB or more |
| Disk | 35 GB or more |
Part 2: Start OpenShift Local
Open a terminal on your machine.
Check your OpenShift Local version:
crc version
Set the OpenShift preset:
crc config set preset openshift
Code language: JavaScript (javascript)
Run the setup command:
crc setup
Start OpenShift Local:
crc start
The start command may take several minutes.
When the cluster starts successfully, OpenShift Local prints login information for the web console and command line.
To view the credentials again, run:
crc console --credentials
Code language: JavaScript (javascript)
To open the web console, run:
crc console
Code language: JavaScript (javascript)
Part 3: Configure the oc CLI
OpenShift Local provides the oc CLI environment command.
Run:
crc oc-env
On Linux or macOS, apply the environment using:
eval $(crc oc-env)
Code language: JavaScript (javascript)
Now log in as the developer user.
Use the password shown by:
crc console --credentials
Code language: JavaScript (javascript)
Login command:
oc login -u developer https://api.crc.testing:6443
Code language: JavaScript (javascript)
Verify login:
oc whoami
Expected output:
developer
Check the cluster:
oc get nodes
Code language: JavaScript (javascript)
Expected result:
NAME STATUS ROLES AGE VERSION
crc-xxxxx-master-0 Ready control-plane,master,worker ...
The exact node name and version may be different.
Part 4: Create a New Project from the Web Console
- Open the OpenShift web console.
- Log in as:
Username: developer
Password: Use the password from crc console --credentials
Code language: JavaScript (javascript)
- Switch to the Developer perspective.
- Click Project dropdown.
- Click Create Project.
- Enter the following project name:
lab9-db-web-console
Code language: JavaScript (javascript)
- Click Create.
You now have a separate OpenShift project for this lab.
Part 5: Create Database Resources Using Import YAML
In the OpenShift web console:
- Make sure you are in the project:
lab9-db-web-console
Code language: JavaScript (javascript)
- Go to +Add.
- Choose Import YAML.
- Paste the full YAML below.
- Click Create.
YAML: PostgreSQL Database, Secret, ConfigMap, PVC, Service, and Client Pod
apiVersion: v1
kind: Secret
metadata:
name: postgresql-secret
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
type: Opaque
stringData:
POSTGRESQL_USER: appuser
POSTGRESQL_PASSWORD: ChangeMeLab9!
POSTGRESQL_DATABASE: appdb
---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgresql-config
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
data:
POSTGRESQL_MAX_CONNECTIONS: "50"
POSTGRESQL_SHARED_BUFFERS: "64MB"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgresql-data
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
labels:
app: postgresql
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
strategy:
type: Recreate
template:
metadata:
labels:
app: postgresql
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
spec:
terminationGracePeriodSeconds: 30
containers:
- name: postgresql
image: registry.redhat.io/rhel9/postgresql-15:latest
imagePullPolicy: IfNotPresent
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
env:
- name: POSTGRESQL_USER
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_USER
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_PASSWORD
- name: POSTGRESQL_DATABASE
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_DATABASE
- name: POSTGRESQL_MAX_CONNECTIONS
valueFrom:
configMapKeyRef:
name: postgresql-config
key: POSTGRESQL_MAX_CONNECTIONS
- name: POSTGRESQL_SHARED_BUFFERS
valueFrom:
configMapKeyRef:
name: postgresql-config
key: POSTGRESQL_SHARED_BUFFERS
- name: POSTGRESQL_LOG_DESTINATION
value: /dev/stderr
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 768Mi
readinessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 2
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/pgsql/data
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: postgresql-data
---
apiVersion: v1
kind: Service
metadata:
name: postgresql
labels:
app: postgresql
app.kubernetes.io/name: postgresql
app.kubernetes.io/part-of: lab9-database-demo
spec:
type: ClusterIP
selector:
app: postgresql
ports:
- name: postgresql
protocol: TCP
port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: db-client
labels:
app: db-client
app.kubernetes.io/name: db-client
app.kubernetes.io/part-of: lab9-database-demo
spec:
replicas: 1
selector:
matchLabels:
app: db-client
template:
metadata:
labels:
app: db-client
app.kubernetes.io/name: db-client
app.kubernetes.io/part-of: lab9-database-demo
spec:
containers:
- name: db-client
image: registry.redhat.io/rhel9/postgresql-15:latest
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -c
args:
- echo "db-client is ready"; while true; do sleep 3600; done
env:
- name: PGHOST
value: postgresql
- name: PGPORT
value: "5432"
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_DATABASE
- name: PGUSER
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: POSTGRESQL_PASSWORD
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Code language: JavaScript (javascript)
Part 6: Understand the YAML
1. Secret
kind: Secret
metadata:
name: postgresql-secret
The Secret stores sensitive database values:
POSTGRESQL_USER
POSTGRESQL_PASSWORD
POSTGRESQL_DATABASE
The PostgreSQL pod uses these values when the database is initialized.
The client pod also uses these values to connect to the database.
In real projects, never hard-code production passwords in YAML files committed to Git.
For production, use a secret management approach such as:
- External Secrets Operator
- HashiCorp Vault
- AWS Secrets Manager
- Azure Key Vault
- Google Secret Manager
- Sealed Secrets
- GitOps secret encryption tools
For this beginner lab, the password is visible so students can understand the workflow.
2. ConfigMap
kind: ConfigMap
metadata:
name: postgresql-config
The ConfigMap stores non-sensitive settings:
POSTGRESQL_MAX_CONNECTIONS
POSTGRESQL_SHARED_BUFFERS
Use ConfigMaps for application configuration that is not secret.
Examples:
APP_MODE=dev
LOG_LEVEL=debug
DATABASE_HOST=postgresql
DATABASE_PORT=5432
Do not store passwords, tokens, or private keys in ConfigMaps.
3. PersistentVolumeClaim
kind: PersistentVolumeClaim
metadata:
name: postgresql-data
The PersistentVolumeClaim requests storage for the database.
The PostgreSQL database writes its data to:
/var/lib/pgsql/data
Code language: JavaScript (javascript)
That directory is backed by the PVC.
This means the data can survive a pod restart or pod recreation.
4. PostgreSQL Deployment
kind: Deployment
metadata:
name: postgresql
The Deployment creates and manages the PostgreSQL pod.
The lab uses:
strategy:
type: Recreate
This is useful for a single-instance database because the old pod is stopped before a new pod is started.
5. PostgreSQL Service
kind: Service
metadata:
name: postgresql
The Service provides a stable internal name for the database.
Other pods in the same project can connect to PostgreSQL using:
postgresql:5432
Code language: CSS (css)
The client pod uses:
PGHOST=postgresql
This is OpenShift/Kubernetes service discovery.
6. DB Client Deployment
kind: Deployment
metadata:
name: db-client
The db-client pod is a test pod. It is not the database. It is used to test database connectivity from inside the cluster.
The client pod has PostgreSQL client tools available, including:
psql
You will use this pod to connect to the PostgreSQL Service.
Part 7: Verify Resources in the Web Console
After clicking Create, go to:
Developer perspective > Topology
You should see two workloads:
postgresql
db-client
Click the postgresql workload.
Check:
- Pod status
- Deployment status
- Service
- Logs
- Events
The PostgreSQL pod should eventually show as running.
Click the db-client workload.
Check that its pod is also running.
Part 8: Verify Resources Using CLI
Run:
oc project lab9-db-web-console
Code language: JavaScript (javascript)
List all major resources:
oc get deploy,po,svc,pvc,secret,cm
Code language: JavaScript (javascript)
Expected resources:
deployment.apps/postgresql
deployment.apps/db-client
pod/postgresql-xxxxx
pod/db-client-xxxxx
service/postgresql
persistentvolumeclaim/postgresql-data
secret/postgresql-secret
configmap/postgresql-config
Check rollout status:
oc rollout status deployment/postgresql
Expected output:
deployment "postgresql" successfully rolled out
Code language: JavaScript (javascript)
Check client rollout:
oc rollout status deployment/db-client
Expected output:
deployment "db-client" successfully rolled out
Code language: JavaScript (javascript)
Check the PVC:
oc get pvc postgresql-data
Code language: JavaScript (javascript)
Expected output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
postgresql-data Bound pvc-xxxxxx 1Gi RWO ...
The STATUS should be:
Bound
Part 9: Check PostgreSQL Logs
From the web console:
- Go to Developer perspective.
- Open Topology.
- Click
postgresql. - Click the pod.
- Open the Logs tab.
Or use CLI:
oc logs deployment/postgresql
You should see PostgreSQL initialization and startup messages.
The exact log output may vary, but it should indicate that PostgreSQL started successfully.
Part 10: Connect to PostgreSQL from the Web Console Terminal
Now connect to the database from inside the OpenShift cluster.
In the web console:
- Go to Developer perspective.
- Open Topology.
- Click
db-client. - Open the running pod.
- Click the Terminal tab.
Inside the terminal, run:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT version();"
Code language: JavaScript (javascript)
Expected output should include PostgreSQL version information.
Example:
PostgreSQL 15.x
Code language: CSS (css)
Now check the current database and current user:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT current_database(), current_user;"
Code language: JavaScript (javascript)
Expected output:
current_database | current_user
------------------+--------------
appdb | appuser
This confirms that the client pod can connect to the PostgreSQL database using the internal service name.
Part 11: Create a Table and Insert Data
From the db-client pod terminal, run:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE"
Code language: JavaScript (javascript)
You should now be inside the PostgreSQL shell.
You will see a prompt similar to:
appdb=>
Code language: PHP (php)
Create a table:
CREATE TABLE IF NOT EXISTS lab9_messages (
id SERIAL PRIMARY KEY,
student_name TEXT NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Code language: PHP (php)
Insert a row:
INSERT INTO lab9_messages(student_name, message)
VALUES ('student01', 'Hello from OpenShift Local Lab 9');
Code language: JavaScript (javascript)
Query the table:
SELECT id, student_name, message, created_at
FROM lab9_messages
ORDER BY id DESC
LIMIT 5;
Expected output:
id | student_name | message | created_at
----+--------------+------------------------------------+----------------------------
1 | student01 | Hello from OpenShift Local Lab 9 | ...
Code language: JavaScript (javascript)
Exit PostgreSQL:
\q
Part 12: One-Line SQL Test Alternative
If copy-paste into the PostgreSQL shell is difficult, run the following one-line commands from the db-client pod terminal.
Create the table:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "CREATE TABLE IF NOT EXISTS lab9_messages (id SERIAL PRIMARY KEY, student_name TEXT NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"
Code language: JavaScript (javascript)
Insert a row:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "INSERT INTO lab9_messages(student_name, message) VALUES ('student01', 'Hello from OpenShift Local Lab 9');"
Code language: JavaScript (javascript)
Query rows:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT id, student_name, message, created_at FROM lab9_messages ORDER BY id DESC LIMIT 5;"
Code language: JavaScript (javascript)
Part 13: Verify Database Connectivity Using CLI
You can also run the SQL test from your local terminal using oc exec.
First, find the db-client pod:
oc get pods -l app=db-client
Code language: JavaScript (javascript)
Store the pod name in a variable:
DB_CLIENT_POD=$(oc get pod -l app=db-client -o jsonpath='{.items[0].metadata.name}')
Code language: PHP (php)
Run a database query:
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT current_database(), current_user;"'
Code language: JavaScript (javascript)
Expected output:
current_database | current_user
------------------+--------------
appdb | appuser
Create the table:
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "CREATE TABLE IF NOT EXISTS lab9_messages (id SERIAL PRIMARY KEY, student_name TEXT NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"'
Code language: JavaScript (javascript)
Insert a row:
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "INSERT INTO lab9_messages(student_name, message) VALUES ('cli-user', 'Inserted using oc exec from local terminal');"'
Code language: JavaScript (javascript)
Query the table:
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT id, student_name, message, created_at FROM lab9_messages ORDER BY id DESC LIMIT 5;"'
Code language: JavaScript (javascript)
Part 14: Prove That the Service Name Works
The database client connects to:
postgresql
This name comes from the OpenShift Service:
kind: Service
metadata:
name: postgresql
Inside the same OpenShift project, pods can reach the database using:
postgresql:5432
Code language: CSS (css)
To inspect the service, run:
oc describe service postgresql
You should see:
Name: postgresql
Type: ClusterIP
Port: postgresql 5432/TCP
Endpoints: ...
Code language: HTTP (http)
The Endpoints field should point to the PostgreSQL pod IP and port.
Part 15: Prove That Data Persists After Pod Restart
A database should not lose data just because the pod restarts.
In this lab, data is stored on the PersistentVolumeClaim named:
postgresql-data
Option A: Restart from Web Console
- Go to Developer perspective.
- Open Topology.
- Click the
postgresqlworkload. - Open the Deployment.
- Scale the Deployment to
0. - Wait until the PostgreSQL pod disappears.
- Scale the Deployment back to
1. - Wait until the new pod is running.
Now go back to the db-client pod terminal and run:
psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT id, student_name, message, created_at FROM lab9_messages ORDER BY id DESC LIMIT 5;"
Code language: JavaScript (javascript)
Your previously inserted row should still exist.
Option B: Restart from CLI
Scale PostgreSQL down:
oc scale deployment/postgresql --replicas=0
Wait:
oc get pods -l app=postgresql
Code language: JavaScript (javascript)
Scale PostgreSQL back up:
oc scale deployment/postgresql --replicas=1
Wait for rollout:
oc rollout status deployment/postgresql
Query the data again:
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT id, student_name, message, created_at FROM lab9_messages ORDER BY id DESC LIMIT 5;"'
Code language: JavaScript (javascript)
If the data is still visible, persistence is working.
Part 16: Check That No Route Was Created
A PostgreSQL database should usually remain internal to the cluster.
Check routes:
oc get route
Code language: JavaScript (javascript)
Expected output:
No resources found in lab9-db-web-console namespace.
Code language: JavaScript (javascript)
This is correct.
The database is reachable inside OpenShift using the Service:
postgresql:5432
Code language: CSS (css)
It is not exposed outside the cluster.
Part 17: Student Checkpoint
At this point, students should be able to answer:
| Question | Expected Answer |
|---|---|
| What stores the database password? | Secret |
| What stores non-sensitive database settings? | ConfigMap |
| What gives the database a stable internal name? | Service |
| What stores database files? | PersistentVolumeClaim |
| What runs the PostgreSQL container? | Deployment |
| What is the internal database hostname? | postgresql |
| What is the PostgreSQL port? | 5432 |
| Should the database have an external Route? | No |
Part 18: Full CLI Validation Script
Run the following commands from your local terminal after logging in with oc.
oc project lab9-db-web-console
echo "Checking deployments..."
oc get deployment
echo "Checking pods..."
oc get pods -o wide
echo "Checking service..."
oc get svc postgresql
echo "Checking PVC..."
oc get pvc postgresql-data
echo "Checking Secret and ConfigMap..."
oc get secret postgresql-secret
oc get configmap postgresql-config
echo "Checking PostgreSQL rollout..."
oc rollout status deployment/postgresql
echo "Checking db-client rollout..."
oc rollout status deployment/db-client
echo "Finding db-client pod..."
DB_CLIENT_POD=$(oc get pod -l app=db-client -o jsonpath='{.items[0].metadata.name}')
echo "DB client pod: $DB_CLIENT_POD"
echo "Running PostgreSQL connectivity test..."
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT current_database(), current_user;"'
echo "Checking existing lab table..."
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT COUNT(*) FROM lab9_messages;"'
Code language: PHP (php)
Expected result:
- Both Deployments should be available.
- Both pods should be running.
- The Service should exist.
- The PVC should be Bound.
- The PostgreSQL query should return
appdbandappuser.
Part 19: Troubleshooting
Problem 1: PostgreSQL Pod Shows ImagePullBackOff
Check the pod:
oc get pods
Code language: JavaScript (javascript)
Describe the pod:
oc describe pod -l app=postgresql
Check events:
oc get events --sort-by=.lastTimestamp
Code language: JavaScript (javascript)
Possible causes:
- Red Hat registry pull secret issue.
- Network issue.
- Image name or tag not available in your environment.
Possible fix for a classroom lab:
Edit the Deployment image from:
registry.redhat.io/rhel9/postgresql-15:latest
to:
registry.access.redhat.com/rhel9/postgresql-15:latest
Then save and wait for the pod to restart.
Problem 2: PVC Is Pending
Check the PVC:
oc get pvc
Code language: JavaScript (javascript)
Describe the PVC:
oc describe pvc postgresql-data
Check storage classes:
oc get storageclass
Code language: JavaScript (javascript)
Possible causes:
- No default StorageClass.
- OpenShift Local storage is not ready.
- Cluster is still starting.
In OpenShift Local, wait a few minutes and check again.
If there is no default StorageClass, ask the instructor to verify the OpenShift Local setup.
Problem 3: PostgreSQL Pod Is CrashLoopBackOff
Check logs:
oc logs deployment/postgresql
Check previous logs:
oc logs deployment/postgresql --previous
Common causes:
- Wrong environment variable names.
- Invalid database initialization.
- PVC already contains old incompatible data.
- Password changed after the database was already initialized.
For a lab reset, delete and recreate the project:
oc delete project lab9-db-web-console
Code language: JavaScript (javascript)
Then create the project again and reapply the YAML.
Problem 4: Password Authentication Failed
If you changed the Secret after PostgreSQL was already initialized, the database password inside the persistent data directory may not change automatically.
For this beginner lab, the simplest reset is:
oc delete project lab9-db-web-console
Code language: JavaScript (javascript)
Then recreate the project and apply the YAML again.
In production, password rotation requires a planned database credential rotation process.
Problem 5: psql Command Not Found
Make sure you are running the command inside the db-client pod, not your laptop terminal.
From CLI:
DB_CLIENT_POD=$(oc get pod -l app=db-client -o jsonpath='{.items[0].metadata.name}')
oc exec -it "$DB_CLIENT_POD" -- bash
Code language: PHP (php)
Then run:
psql --version
Problem 6: No Route Found
This is expected.
Run:
oc get route
Code language: JavaScript (javascript)
Expected output:
No resources found
PostgreSQL is not an HTTP web application. It should normally be accessed internally through a Service, not exposed externally through a Route.
Part 20: Cleanup
To delete all resources created in this lab, delete the project.
From the web console:
- Switch to Administrator perspective.
- Go to Home > Projects.
- Find:
lab9-db-web-console
Code language: JavaScript (javascript)
- Delete the project.
Or use CLI:
oc delete project lab9-db-web-console
Code language: JavaScript (javascript)
Verify:
oc get project lab9-db-web-console
Code language: JavaScript (javascript)
The project should no longer exist.
Part 21: Lab Summary
In this lab, you created a database-backed OpenShift environment using the web console.
You created:
| Resource | Purpose |
|---|---|
| Secret | Stored database credentials |
| ConfigMap | Stored non-sensitive database configuration |
| PersistentVolumeClaim | Stored database data persistently |
| Deployment | Ran the PostgreSQL database |
| Service | Provided internal DNS name for PostgreSQL |
| Client Deployment | Tested database connectivity |
You verified that:
- PostgreSQL runs inside OpenShift.
- The database is reachable by service name.
- Credentials can be injected from a Secret.
- Non-sensitive settings can be injected from a ConfigMap.
- Database data persists after pod restart.
- The database does not need an external Route.
Part 22: Lab Review Questions
Question 1
What is the purpose of a Secret in this lab?
Answer
A Secret stores sensitive data such as the database username, password, and database name.
Question 2
What is the purpose of a ConfigMap in this lab?
Answer
A ConfigMap stores non-sensitive configuration such as database tuning values or application settings.
Question 3
Why do we use a PersistentVolumeClaim?
Answer
A PersistentVolumeClaim provides persistent storage so database data is not lost when the pod restarts.
Question 4
Why does PostgreSQL use a Service?
Answer
The Service provides a stable internal DNS name and port for other pods to connect to PostgreSQL.
Question 5
Why did we not create a Route for PostgreSQL?
Answer
A Route is mainly used to expose HTTP or HTTPS applications outside the cluster. PostgreSQL is a database service and should normally remain internal to the cluster.
Question 6
What hostname does the client pod use to connect to PostgreSQL?
Answer
postgresql
Question 7
What port does PostgreSQL listen on?
Answer
5432
Question 8
Which resource runs the PostgreSQL container?
Answer
Deployment.
Question 9
Which resource stores the PostgreSQL data files?
Answer
PersistentVolumeClaim.
Question 10
What command can you use to verify the PostgreSQL pod logs?
Answer
oc logs deployment/postgresql
Part 23: Instructor Notes
This lab is designed as a replacement for older OpenShift database labs that used deprecated or outdated patterns.
The lab intentionally avoids:
- MongoDB 3.6
- DeploymentConfig
- Ephemeral-only database storage
- Hard-coded database URLs in application configuration
- Exposing a database using a Route
- Old OpenShift template-based database provisioning
The lab teaches current OpenShift concepts:
- Deployment
- Service
- Secret
- ConfigMap
- PersistentVolumeClaim
- Internal service discovery
- Pod terminal access
- CLI validation with
oc exec
For production environments, students should also learn:
- StatefulSet for production-grade stateful workloads
- Database Operators
- Automated backup and restore
- Monitoring and alerting
- Secret rotation
- TLS for database connections
- NetworkPolicy
- Resource sizing
- High availability
- Disaster recovery
This lab is intentionally simple because it is focused on beginner-level OpenShift database fundamentals using the web console.
Part 24: Instructor Validation Checklist
Before giving this lab to students, the instructor should run:
crc version
crc status
oc version
oc whoami
oc get nodes
oc get storageclass
Code language: JavaScript (javascript)
Then apply the lab and validate:
oc project lab9-db-web-console
oc get deploy,po,svc,pvc,secret,cm
oc rollout status deployment/postgresql
oc rollout status deployment/db-client
oc logs deployment/postgresql
Code language: JavaScript (javascript)
Then run:
DB_CLIENT_POD=$(oc get pod -l app=db-client -o jsonpath='{.items[0].metadata.name}')
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT version();"'
oc exec "$DB_CLIENT_POD" -- bash -lc 'psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c "SELECT current_database(), current_user;"'
Code language: PHP (php)
If these commands pass, the lab is ready for students.
Final Outcome
You have successfully completed the updated OpenShift Lab 9.
You deployed a PostgreSQL database using the OpenShift web console, configured it using Secret and ConfigMap, stored its data on a PersistentVolumeClaim, exposed it internally using a Service, and tested database connectivity from another pod inside the cluster.
Iโm a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services โ all in one place.
Explore Hospitals
The lab demonstrates how to work with databases through the OpenShift web console, but it could also address operational concerns that become important beyond a learning environment. Topics such as persistent storage selection, backup and recovery strategies, secret rotation, and database upgrades are critical for running stateful workloads reliably. It would also be helpful to discuss monitoring database health, tracking resource consumption, and planning for high availability to ensure applications remain resilient as usage grows.