Last updated: October 2026
In this lesson, you will learn how to deploy application in OpenShift cluster, and at the end of this lesson, you will also practice the recent ex280 exam practice questions relating to this topic to reinforce your understanding and prepare for the Red Hat Certified OpenShift exam.
Understanding The Subject Matter
Deploying an application on Red Hat OpenShift Container Platform involves more than starting a container. You must create the necessary Kubernetes resources, ensure the application is running, and configure a way for users or other applications to access it.
OpenShift provides different ways to accomplish these tasks. You can use imperative commands through the OpenShift CLI (oc), or you can define the desired configuration in YAML files and apply it declaratively.
In this guide, we will use both methods to deploy a simple web application. You will learn how to create an OpenShift project, deploy an application, understand the resources OpenShift creates, andverify that the application is running.
What You Will Learn
By the end of this tutorial, you should be able to:
- Create and switch between OpenShift projects.
- Explain the difference between a Pod, Deployment, ReplicaSet, Service, and Route.
- Deploy an application using imperative commands.
- Deploy the same application using declarative YAML manifests.
- Verify application health and troubleshoot common deployment problems.
- Understand when to use imperative and declarative approaches.
Prerequisites
Before proceeding, ensure you have:
- Access to a running OpenShift Container Platform cluster.
- The OpenShift CLI (
oc) installed on your workstation. - Credentials that allow you to log in and create resources in a project.
- Network access to pull the container image used in this tutorial.
- An OpenShift cluster with a configured router if you want to expose the application externally.
The commands in this guide are run from a workstation, not directly on an OpenShift node. You do not need to SSH into a cluster node to deploy or test the application.
The examples use the nginxinc/nginx-unprivileged:stable container image, which listens on port 8080 and is designed to run without root privileges. The examples assume that this image is available from your cluster and that the tag has not changed unexpectedly. For production, pin and validate an appropriate image version or digest.
Note: Command outputs are examples. Your cluster IP addresses, Pod names, route hostnames, and resource ages will be different.
1. Understanding Application Deployment in OpenShift
An application deployed on OpenShift commonly consists of several resources that work together.
| Resource | Purpose |
|---|---|
| Project | Groups and isolates application resources. |
| Deployment | Describes the desired application state, including the container image and number of replicas. |
| ReplicaSet | Maintains the required number of Pod replicas for a Deployment. |
| Pod | Runs one or more containers. |
| Service | Provides a stable network endpoint for accessing application Pods inside the cluster. |
| Route | Exposes a Service through a hostname that can be reached through the OpenShift router. Some environment uses Ingress or Istio to expose applications |
A Deployment does not run the container directly. It manages ReplicaSets, which maintain the required Pods. The Pods run the application’s containers.
A Service provides a stable endpoint even when individual Pods are replaced and receive different IP addresses. A Route provides a way to reach the Service from outside the cluster, subject to DNS, routing, and network configuration.
The general relationship is:
Deployment → ReplicaSet → Pods ← Service ← Route
The Service selects the appropriate Pods using labels. The Route directs incoming requests to the Service.
What is the difference between a Deployment and a DeploymentConfig?
A Deployment is a Kubernetes resource that OpenShift supports and recommends for most new application workloads.
A DeploymentConfig is an OpenShift-specific resource that uses a different deployment mechanism, including ReplicationControllers. It remains relevant to existing environments and workloads that depend on its specific features, but it should not be introduced as the default method for every new application.
In this tutorial, we will use a standard Kubernetes Deployment.

ACTION TIME
2. Log in to the OpenShift Cluster
Before creating resources, authenticate to the cluster.
If you are using the OpenShift web console, you can usually copy a login command from your user menu. Use the command and token supplied by your cluster rather than copying credentials from an example.
[tekneed@osl ~]$ oc login https://api.ocp.example.com:6443
Example output:
Authentication required for https://api.ocp.example.com:6443
Username: learner
Password:
Login successful.
You have access to projects on this server.
The exact output depends on your authentication method and permissions.
Confirm that you are connected to the expected cluster:
[tekneed@osl ~]$ oc whoami
Example output:
learner
You can also inspect the active server:
[tekneed@osl ~]$ oc whoami --show-server
Example output:
https://api.ocp.example.com:6443
Always verify that you are connected to the correct cluster before creating or deleting resources.
3. Create an OpenShift Project
An OpenShift project is an environment for organizing application resources. It is closely related to a Kubernetes namespace, with additional OpenShift-specific capabilities.
For this exercise, create a project called app-project.
[tekneed@osl ~]$ oc new-project app-project
Example output:
Now using project "app-project" on server
"https://api.ocp.example.com:6443".
You can add applications to this project with the 'new-app' command.
Confirm the active project:
[tekneed@osl ~]$ oc project
Example output:
Using project "app-project" on server
"https://api.ocp.example.com:6443".
You can list the projects available to your account using:
[tekneed@osl ~]$ oc projects
Example output:
You have access to the following projects and can switch between them:
default
app-project
openshift
openshift-ingress
...
Your output will depend on your account permissions.
Important: Most commands in this tutorial operate on the current project unless you explicitly specify another namespace. Confirm the active project before creating resources.
4. Deploy an Application Using the Imperative Approach
An imperative approach uses commands to tell OpenShift what action to perform.
For example:
[tekneed@osl ~]$ oc create deployment nginx-app \
--image=nginxinc/nginx-unprivileged:stable \
--port=8080
This command asks OpenShift to create a Deployment named nginx-app using the specified container image.
Imperative commands are convenient for learning, quick experiments, and one-off operations. They can also be used in scripts, although scripts need appropriate error handling and validation.
4.1 Create the Deployment
Run the command:
[tekneed@osl ~]$ oc create deployment nginx-app \
--image=nginxinc/nginx-unprivileged:stable \
--port=8080
Example output:
deployment.apps/nginx-app created
You have created the Deployment, but that does not necessarily mean the application is ready to serve traffic. OpenShift must still schedule the Pod, pull the image if necessary, and start the container.
4.2 Check the Deployment
[tekneed@osl ~]$ oc get deployments
Example output:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 0/1 1 0 10s
The Deployment may initially show 0/1 because the Pod is still starting. Wait briefly and check again:
[tekneed@osl ~]$ oc get deployments
Example output once the application is ready:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 1/1 1 1 45s
Here is what the columns mean:
- READY: The number of ready replicas compared with the desired number.
- UP-TO-DATE: The number of replicas updated to the Deployment’s current specification.
- AVAILABLE: The number of replicas available to serve requests.
- AGE: How long the resource has existed.
The output may take longer to reach 1/1 if image downloads are slow or the cluster has scheduling problems.
4.3 Check the Pod
A Deployment creates and manages Pods indirectly through a ReplicaSet.
[tekneed@osl ~]$ oc get pods
Example output:
NAME READY STATUS RESTARTS AGE
nginx-app-6b7f8c9d7f-abcde 1/1 Running 0 45s
Notice that the Pod is not named simply nginx-app. The suffix is generated as part of the naming structure used by the Deployment and ReplicaSet.
The Deployment name remains stable even if a Pod is deleted and replaced.
To display additional details, including the node on which the Pod is scheduled, run:
[tekneed@osl ~]$ oc get pods -o wide
Example output:
NAME READY STATUS RESTARTS AGE IP NODE
nginx-app-6b7f8c9d7f-abcde 1/1 Running 0 45s 10.128.2.15 worker-01
The Pod IP is an internal cluster address. It is not normally the address you give external users to access the application.
4.4 Understand the ReplicaSet
Check the ReplicaSet created by the Deployment:
[tekneed@osl ~]$ oc get replicasets
Example output:
NAME DESIRED CURRENT READY AGE
nginx-app-6b7f8c9d7f 1 1 1 45s
The ReplicaSet helps maintain the required number of Pods. The Deployment manages the ReplicaSet and supports operations such as rolling updates and rollbacks.
This is one reason a Deployment is generally preferable to creating a standalone Pod for a long-running application.
4.5 Inspect the Deployment
To examine the Deployment and its recent events, run:
[tekneed@osl ~]$ oc describe deployment nginx-app
The output contains information such as the container image, desired replicas, available replicas, labels, and events.
You can also inspect a Pod:
[tekneed@osl ~]$ oc describe pod nginx-app-6b7f8c9d7f-abcde
Replace the example Pod name with the one returned by oc get pods.
If the Pod does not start, the Events section often helps explain why. For example, it may report an image pull failure, an insufficient resource error, or a security-related issue.
5. Create a Service to Access the Application
At this point, the application is running in a Pod, but you have not yet created a stable network endpoint for it.
A Service provides a stable endpoint and forwards traffic to the matching Pods.
Create a Service for the Deployment:
[tekneed@osl ~]$ oc expose deployment nginx-app --port=8080
Example output:
service/nginx-app exposed
The Service uses port 8080 and selects the Pods associated with the Deployment. The container image used in this exercise listens on port 8080.
Inspect the Service:
[tekneed@osl ~]$ oc get services
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-app ClusterIP 172.30.45.120 <none> 8080/TCP 10s
A ClusterIP Service is accessible from within the cluster. The EXTERNAL-IP field being <none> is expected for this Service type.
You can inspect the Service’s endpoints as well:
[tekneed@osl ~]$ oc get endpoints nginx-app
Depending on the OpenShift and Kubernetes version, you may see the older Endpoints resource or use EndpointSlices:
[tekneed@osl ~]$ oc get endpointslices \
-l kubernetes.io/service-name=nginx-app
The endpoint information should identify the backend Pod addresses and port when the Pod is ready and selected by the Service.
If no backend endpoints appear, check that the Pod is ready and that the Service selector matches the Pod labels.
6. Expose the Application Through an OpenShift Route
A Service is normally used for internal cluster access. To make this web application reachable through the OpenShift router, create a Route.
Run:
[tekneed@osl ~]$ oc expose service nginx-app
Example output:
route.route.openshift.io/nginx-app exposed
Now retrieve the route hostname:
[tekneed@osl ~]$ oc get routes
Example output:
NAME HOST/PORT PATH SERVICES PORT
nginx-app nginx-app-app-project.apps.ocp.example.com nginx-app 8080
The hostname shown above is an example. Your cluster will generate a hostname based on its configured application domain unless you specify a custom host.
The route depends on the OpenShift router, DNS resolution, and any relevant network or firewall rules being configured correctly.
6.1 Test the Application
You can test the route from your workstation using curl:
[tekneed@osl ~]$ curl -I \
http://nginx-app-app-project.apps.ocp.example.com
Example output:
HTTP/1.1 200 OK
Content-Type: text/html
...
Use the actual hostname returned by oc get routes.
You can also open the URL in a browser. If the route is configured for HTTPS, use the HTTPS URL and ensure the certificate is trusted.
A successful HTTP response confirms that the request reached a web server through the route. If the request fails, check the route hostname, DNS, router configuration, Service, Pod readiness, and the application’s listening port.
At this stage, you have deployed the application imperatively and created the resources required to access it.
7. Scale the Application
One advantage of using a Deployment is that you can change the desired number of application replicas without manually creating individual Pods.
Scale the application to two replicas:
[tekneed@osl ~]$ oc scale deployment nginx-app --replicas=2
Example output:
deployment.apps/nginx-app scaled
Check the Deployment:
[tekneed@osl ~]$ oc get deployments
Example output:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 2/2 2 2 5m
Check the Pods:
[tekneed@osl ~]$ oc get pods
Example output:
NAME READY STATUS RESTARTS AGE
nginx-app-6b7f8c9d7f-abcde 1/1 Running 0 5m
nginx-app-6b7f8c9d7f-fghij 1/1 Running 0 20s
Your actual Pod names will differ.
The Service continues to provide a stable endpoint and can distribute requests across the ready Pods selected by its selector. The Route does not need to be recreated just because the Deployment has more replicas.
Scaling replicas improves capacity and can help with availability, but two replicas alone do not guarantee high availability. Node placement, resource availability, application behavior, storage, and other design considerations also matter.
8. Deploy an Application Using the Declarative Approach
The imperative approach uses commands to perform actions directly. The declarative approach describes the desired state in configuration files, commonly YAML, and submits that configuration to OpenShift.
For example, instead of issuing separate commands to create a Deployment, Service, and Route, you can define those resources in YAML and apply them.
This is useful because the configuration can be saved in version control, reviewed before changes are made, reused in other environments, and applied repeatedly.
If you want a deeper explanation of managing OpenShift resources with YAML, see the companion guide:
Create and Manage OpenShift Resources Using the Declarative Approach:
https://tekneed.com/create-manage-openshift-resources-by-declarative/
For this exercise, we will deploy the same application using declarative manifests.
8.1 Create a YAML Manifest
Create a file called nginx-app.yaml on your workstation.
For example:
[tekneed@osl ~]$ vi nginx-app.yaml
Add the following content:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
labels:
app: nginx-app
spec:
replicas: 2
selector:
matchLabels:
app: nginx-app
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:stable
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: nginx-app
labels:
app: nginx-app
spec:
selector:
app: nginx-app
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: nginx-app
spec:
to:
kind: Service
name: nginx-app
port:
targetPort: http
Save the file.
The manifest defines three resources:
- Deployment: Runs two replicas of the NGINX application.
- Service: Provides a stable internal endpoint on port
8080. - Route: Exposes the Service through the OpenShift router.
The --- separators divide multiple YAML documents within the same file.
The selector.matchLabels field in the Deployment must match the labels assigned to the Pod template. The Service selector must also match the Pod labels. These relationships allow the Deployment to manage its Pods and the Service to direct traffic to them.
The Route’s targetPort refers to the named Service port http.
Because the file contains an OpenShift-specific Route resource, it is intended for OpenShift Container Platform rather than a standard Kubernetes cluster without the OpenShift Route API.
8.2 Review the Manifest Before Applying It
Before creating resources, you can ask OpenShift to validate the configuration on the server without persisting it.
[tekneed@osl ~]$ oc apply --dry-run=server -f nginx-app.yaml
The output should indicate that the resources would be created or configured, without saving them to the cluster.
This check requires access to the relevant API resources and permissions. It does not guarantee that the image will successfully start or that the Route will be reachable.
8.3 Apply the YAML File
Run:
[tekneed@osl ~]$ oc apply -f nginx-app.yaml
Example output for a first deployment:
deployment.apps/nginx-app created
service/nginx-app created
route.route.openshift.io/nginx-app created
If the resources already exist and the manifest changes their configuration, the output may say configured instead of created. If there are no changes, it may say unchanged.
This is an important difference between creating resources once and applying a declarative configuration repeatedly.
8.4 Verify the Resources
Check the Deployment:
[tekneed@osl ~]$ oc get deployment nginx-app
Example output:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 2/2 2 2 60s
Check the Pods:
[tekneed@osl ~]$ oc get pods -l app=nginx-app
Example output:
NAME READY STATUS RESTARTS AGE
nginx-app-6b7f8c9d7f-abcde 1/1 Running 0 60s
nginx-app-6b7f8c9d7f-fghij 1/1 Running 0 60s
Check the Service and Route:
[tekneed@osl ~]$ oc get service,route
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-app ClusterIP 172.30.45.120 <none> 8080/TCP 60s
NAME HOST/PORT
route.route.openshift.io/nginx-app nginx-app-app-project.apps.ocp.example.com
Test the application using the hostname shown by oc get routes:
[tekneed@osl ~]$ curl -I \
http://nginx-app-app-project.apps.ocp.example.com
Example output:
HTTP/1.1 200 OK
Content-Type: text/html
...
The example output assumes the application has become ready and the Route is reachable.
8.5 Update the Application Declaratively
Suppose you want to scale the application from two replicas to three.
Open nginx-app.yaml and change:
spec:
replicas: 2
to:
spec:
replicas: 3
Then apply the file again:
[tekneed@osl ~]$ oc apply -f nginx-app.yaml
Example output:
deployment.apps/nginx-app configured
service/nginx-app unchanged
route.route.openshift.io/nginx-app unchanged
Check the Deployment:
[tekneed@osl ~]$ oc get deployment nginx-app
Example output after the rollout completes:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 3/3 3 3 4m
OpenShift reconciles the Deployment toward the desired replica count in the manifest.
This illustrates the main benefit of declarative management: you update the configuration describing the intended state and apply it, rather than having to remember every individual command required to reach that state.
Important: oc apply is not a guarantee that every live field will be reset to the file’s values. The result depends on the resource, field ownership, and the type of change. Avoid manually changing the same fields in multiple management systems without a clear ownership strategy.
9. Imperative vs Declarative: What Is the Difference?
Both approaches can create and manage OpenShift resources. The main difference is how you express the desired result.
| Area | Imperative | Declarative |
|---|---|---|
| How it works | Commands specify actions to perform. | YAML files describe the desired resource state. |
| Example | oc scale deployment nginx-app --replicas=3 | Change replicas in YAML and run oc apply -f nginx-app.yaml. |
| Learning | Convenient for understanding individual commands and experimenting. | Helps learners understand resource specifications and configuration. |
| Reuse | Commands can be saved in scripts. | Manifests can be versioned and reused across environments. |
| Review | Changes are expressed as commands. | Changes can be reviewed in the configuration file before applying. |
| Repeated use | Some create commands fail if the resource already exists. | oc apply can create resources or update supported configuration fields. |
| Common use | Troubleshooting, quick operations, and one-off changes. | Repeatable deployments, Git-based workflows, and configuration management. |
For example, these two operations both aim to scale the same Deployment to three replicas.
Imperative:
[tekneed@osl ~]$ oc scale deployment nginx-app --replicas=3
Declarative:
spec:
replicas: 3
After saving the updated manifest:
[tekneed@osl ~]$ oc apply -f nginx-app.yaml
In a declarative workflow, the YAML file should remain the source of truth. If you change the replica count with an imperative command but leave the file unchanged, a later application of the old manifest may return the Deployment to the replica count specified in that file.
Neither approach is automatically secure or production-ready. Both require appropriate access controls, image management, resource limits, monitoring, and change management.
10. Troubleshooting Common Deployment Problems
A command reporting that a resource was created does not necessarily mean that the application is ready. The following commands help identify common problems.
Problem 1: The Pod Is Stuck in Pending
Check the Pods:
[tekneed@osl ~]$ oc get pods
Inspect the affected Pod:
[tekneed@osl ~]$ oc describe pod <pod-name>
Look at the Events section for messages about insufficient CPU or memory, scheduling restrictions, unbound PersistentVolumeClaims, or other scheduling problems.
Replace <pod-name> with the actual Pod name.
Problem 2: The Pod Is in ImagePullBackOff
Inspect the Pod:
[tekneed@osl ~]$ oc describe pod <pod-name>
Possible causes include:
- An incorrect image name or tag.
- A registry that cannot be reached.
- Registry authentication problems.
- Network restrictions or certificate issues.
Check the exact event message before changing registry credentials or other settings.
Problem 3: The Container Is Restarting
Check the Pod:
[tekneed@osl ~]$ oc get pods
Inspect the logs:
[tekneed@osl ~]$ oc logs <pod-name>
If the container has restarted, retrieve the previous container’s logs when available:
[tekneed@osl ~]$ oc logs <pod-name> --previous
Also inspect the Pod events and termination reason. A restart alone does not tell you whether the cause is an application error, a failed health check, an out-of-memory termination, or another issue.
Problem 4: The Deployment Is Not Ready
Check its status:
[tekneed@osl ~]$ oc rollout status deployment/nginx-app
Inspect the Deployment:
[tekneed@osl ~]$ oc describe deployment nginx-app
Check the ReplicaSet and Pods to determine whether the desired number of replicas can be created and become ready.
Problem 5: The Route Exists but the Application Cannot Be Reached
Inspect the Route and Service:
[tekneed@osl ~]$ oc get route nginx-app
[tekneed@osl ~]$ oc describe route nginx-app
[tekneed@osl ~]$ oc describe service nginx-app
Check the Pod labels and readiness, Service selectors and endpoints, route hostname, DNS resolution, and router reachability. Also verify that the Service’s target port matches the port on which the application is listening.
A Route object can exist even when the application behind it is unhealthy or inaccessible.
11. Clean Up the Lab Resources
When you finish, you can delete the resources created by the declarative manifest:
[tekneed@osl ~]$ oc delete -f nginx-app.yaml
Example output:
deployment.apps "nginx-app" deleted
service "nginx-app" deleted
route.route.openshift.io "nginx-app" deleted
This removes the resources described in the file. It does not delete the project itself.
If you created additional resources outside the manifest, they may remain.
To delete the entire project and its namespaced resources, use:
[tekneed@osl ~]$ oc delete project app-project
Example output:
project.project.openshift.io "app-project" deleted
Caution: Deleting a project removes its namespaced resources, not just this sample application. Only delete it if you created it for this exercise and have confirmed that nothing else needs to remain.
Conclusion
Deploying an application on OpenShift involves creating the resources that run the application and provide network access to it. A Deployment manages the desired number of Pods, a Service provides a stable internal endpoint, and an OpenShift Route can expose the application through the cluster’s router.
The imperative approach is useful for learning, quick operations, and troubleshooting. The declarative approach is valuable when you want configuration that can be reviewed, stored in version control, reused, and applied consistently.
For further study, continue with the companion lesson on creating and managing OpenShift resources using the declarative approach. Understanding both methods will help you work more effectively with OpenShift in development, administration, and DevOps workflows.
Leave a Reply