K8s YAML Generation Guide
On KIOPS's [Service Management] page, you don't need to write Kubernetes deployment YAML manually. Select options in a form and KIOPS generates standard YAML automatically — you can preview it or edit it directly.
This guide starts from the simplest "Hello World" deployment and progressively adds features across 5 steps. Each step keeps the YAML from the previous step intact and adds just one new resource, so even newcomers to Kubernetes can follow along without being overwhelmed.
Why Read This Guide
Even though KIOPS generates the YAML for you, understanding what shows up in the preview lets you catch misconfigurations before deployment. And when special requirements come up, you may need to edit the auto-generated YAML directly. This guide is a learning resource that maps the options you select in the form to the parts of YAML they produce.
Accessing the YAML Generation Page
- Go to the [Service Management] page and select a service.
- Click the Deploy stage in the pipeline.
- Select K8s deployment in Deploy Environment Settings to see the YAML generation form.
- Steps 1 through 5 should be read in order. We start with a simple static site and expand to databases and one-time jobs.
- Appendix: Kind Reference is a glossary of options. Look up only what you need while or after going through the walkthrough.
- Every YAML example has
# [Required]or# [Optional]comments so you can see at a glance which values you must provide.
Before You Start: 10 Terms to Know
Before diving in, here is a definition plus an everyday analogy for each term that shows up repeatedly. Each Step will remind you to revisit this section when a term first appears with "(See: Before You Start)".
| Term | Definition + everyday analogy |
|---|---|
| Container | An isolated environment that runs one application. Like a single bento box compartment — the contents are isolated from other compartments. |
| Image | A snapshot (template) used to create a container. Like a waffle iron — you can stamp out many identical waffles from one mold. |
| Pod | The smallest deployable unit in Kubernetes — one or more containers running together. Like a single apartment unit — the containers inside share the same address and resources. |
| Namespace | A partition inside a cluster that separates resources (think of it as a folder). Like a department within a company — sharing the same building (cluster) but organized by department. |
| Cluster | A group of nodes (servers) that Kubernetes manages as one. Like an apartment complex — multiple buildings (nodes) managed as one. |
| YAML indentation | Use 2 spaces for indentation, no tab key. Like a report with misaligned bullet points, even one mis-indented space breaks the entire structure. |
| Label | A name tag attached to a Pod or resource. Example: app: hello-world. Like a name tag on resources — used to group resources that share the same tag. |
| Deployment | A manager that keeps a fixed number of Pods alive. Like a building manager — automatically fills empty units (Pods) as they go vacant. |
| Service | A permanent address for your Pods. Like a permanent postal code — always reaches a live Pod even when individual Pods change. |
| Secret | The safe-deposit-box version of a ConfigMap. Holds sensitive info like passwords and API keys. |
Step 1: Hello World Deployment (Deployment + Service)
Prerequisite: None (starting point)
What you will learn in this step: The simplest form — running a Pod and making it accessible from inside the cluster.
Why is this needed
If you run a container directly, there is no automatic recovery on failure, and when a Pod restarts its IP changes, breaking other components that depend on it.
- Deployment maintains the desired number of Pods and restarts them on failure.
- Service provides a stable address that does not change even when Pod IPs do.
Without a Service, even other Pods in the same cluster have no reliable way to reach this Pod.
(See the Glossary for Container / Pod / Namespace / Cluster.)
- Name (
metadata.name): Resource identifier. You need one for the Deployment and one for the Service. - Image (
spec.containers[].image): Container image reference. Pick from your build list or enter directly. - Label matching (
selector.matchLabelsandtemplate.metadata.labels):labelsare the name tags on the Pod;selector.matchLabelsis the manager's declaration "I'm in charge of anything with these tags". If the two values differ, the manager is looking for someone else and the Pod ends up abandoned. - Port wiring (
containerPort, plus the Service'sport/targetPort):targetPortmust equal the container'scontainerPortfor traffic to reach your app.
Want more options? → Appendix: Deployment, Appendix: Service
- selector / labels mismatch: If
matchLabelshasapp: hello-worldbuttemplate.metadata.labelshas a different value, the Pod is up but the Service's Endpoint stays empty — you get 502 errors or "connection refused". Always change both together. - Confusing
portandtargetPort:portis what the Service exposes externally;targetPortis the port the container listens on internally. If the container listens on 8080 but you settargetPort: 80, you'll see "Connection refused". Check the container image's EXPOSE port and align them.
KIOPS Configuration Summary
| Field | Value | YAML Field |
|---|---|---|
| Deployment name | hello-world | metadata.name (Deployment) |
| Namespace | my-app | metadata.namespace |
| Replicas | 1 | spec.replicas |
| Image | nginx:1.25 | spec.template.spec.containers[0].image |
| Container port | 80 | containers[0].ports[0].containerPort |
| Service name | hello-world-svc | metadata.name (Service) |
| Service type | ClusterIP | spec.type (Service) |
Generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world # [Required] Name (see new options above)
namespace: my-app # [Optional] default: default
labels:
app: hello-world # [Optional] label for grouping
spec:
replicas: 1 # [Optional] default: 1, number of Pod replicas to run concurrently
selector:
matchLabels:
app: hello-world # [Required] Label matching - must match template.metadata.labels
template:
metadata:
labels:
app: hello-world # [Required] Label matching - matched by selector.matchLabels
spec:
containers:
- name: web # [Required] container name
image: nginx:1.25 # [Required] Image (see new options above)
ports:
- containerPort: 80 # [Optional] Port wiring - port the container listens on
---
apiVersion: v1
kind: Service
metadata:
name: hello-world-svc # [Required] Service name
namespace: my-app # [Optional] default: default
spec:
type: ClusterIP # [Optional] default: ClusterIP
selector:
app: hello-world # [Required] Label matching - must match Deployment Pod labels
ports:
- name: http # [Optional] port identifier
protocol: TCP # [Optional] default: TCP
port: 80 # [Required] Port wiring - port the Service listens on
targetPort: 80 # [Required] Port wiring - container port traffic is forwarded to
What's next
The Service from Step 1 is reachable only inside the cluster. In Step 2 we add an Ingress so external users can access it via a domain name.
Step 2: External Access via Domain (Adding Ingress)
Prerequisite: Step 1 YAML complete
What you will learn in this step: How to expose the application at a domain like app.example.com for external users.
(See Glossary for Cluster and Namespace.)
Why is this needed
The ClusterIP Service from Step 1 is for internal use only. To let external users open a browser and reach the app, you need one of:
- Ingress (this step, recommended): Manages domain-based routing and TLS certificates in one place.
- A NodePort or LoadBalancer Service: Awkward for domain handling and TLS setup.
Without an Ingress, you would have to memorize a different IP per domain, and your application would need to handle HTTPS certificates itself.
- Host (
spec.rules[].host): The domain users will enter. DNS must already point this domain to your cluster's Ingress Controller IP. Ask your IT team or ops team to configure this. - Path and backend Service (
paths[].backend.service.name/port.number): These determine which URL goes to which Service. The Service name and port must exactly match what you created in Step 1. - pathType: With
Prefix,/apimatches/api,/api/users, and/api/v1/data. WithExact, only/apiitself matches.Prefixis what you'll use most of the time. - Ingress Class (
spec.ingressClassName): Selects which Ingress Controller will handle this Ingress. KIOPS recommendstraefikby default.
Want more options? → Appendix: Ingress
- Path order: If
/comes before/apiinpaths, requests to/apialso match/. Put the more specific path (/api) first. - Missing DNS setup: The domain must point to the cluster's Ingress Controller IP via DNS. Ask your IT team or ops team to handle this.
KIOPS Configuration Summary
Keep the Step 1 YAML as is and add one Ingress.
| Field | Value | YAML Field |
|---|---|---|
| Ingress name | hello-world-ingress | metadata.name |
| Namespace | my-app | metadata.namespace |
| Ingress Class | traefik | spec.ingressClassName |
| Host | app.example.com | spec.rules[0].host |
| Path | / → hello-world-svc:80 | spec.rules[0].http.paths[0].path + backend.service |
Added Ingress YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-world-ingress # [Required] Ingress name
namespace: my-app # [Optional] default: default
spec:
ingressClassName: traefik # [Optional] Ingress Class (see new options above)
rules:
- host: app.example.com # [Required] Host (see new options above)
http:
paths:
- path: / # [Required] Path and backend Service - URL path to match
pathType: Prefix # [Required] Prefix (prefix match) or Exact
backend:
service:
name: hello-world-svc # [Required] Path and backend Service - target Service name
port:
number: 80 # [Required] Path and backend Service - target Service port
For HTTPS, add spec.tls with secretName and hosts, and create a Secret containing the certificate for that domain in advance. See the Domain/SSL setup guide for details.
What's next
So far we have used the nginx image as-is. In Step 3 we add a ConfigMap to inject configuration values into the application as environment variables.
Step 3: Externalize Configuration (Adding ConfigMap)
Prerequisite: Step 2 YAML complete
What you will learn in this step: How to move environment variables out of the Deployment YAML into a ConfigMap.
(If environment variables are unclear, ask your IT team or ops team. See Glossary for Secret.)
Why is this needed
If you put environment variables directly in the Deployment YAML you face several problems:
- You need separate Deployments per environment (dev / prod).
- Multiple Deployments cannot share the same configuration.
- Changing only a setting forces you to edit the whole Deployment.
A ConfigMap lets you manage settings in one place, and envFrom injects all of them at once without having to map keys one by one.
ConfigMaps store values in plain text. Put sensitive data — passwords, API keys, tokens — in a Secret and inject it the same way (envFrom: secretRef).
- ConfigMap data (
data): Key-value pairs. All values are strings — wrap numbers and booleans in quotes. - envFrom + configMapRef: Every key in the ConfigMap becomes an environment variable with the matching value. No need to map keys one by one.
- Name match: The Deployment's
configMapRef.namemust exactly match the ConfigMap'smetadata.name, and they must be in the same namespace.
Want more options? → Appendix: ConfigMap
- Namespace mismatch: If you see an error saying the ConfigMap cannot be found, make sure the Deployment and ConfigMap share the same
namespace. Cross-namespace references are not allowed. - Storing secrets in a ConfigMap: Passwords or API keys in a ConfigMap show up as plain text in the YAML file. Always use a Secret instead.
KIOPS Configuration Summary
Add one ConfigMap to the Step 2 YAML and add envFrom to the Deployment.
| Field | Value | YAML Field |
|---|---|---|
| ConfigMap name | app-config | metadata.name (ConfigMap) |
| Data | WELCOME_MESSAGE, LOG_LEVEL, FEATURE_FLAG | data |
| Deployment change | Inject all of app-config via envFrom | containers[0].envFrom[0].configMapRef.name |
Added / changed YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config # [Required] ConfigMap name
namespace: my-app # [Optional] default: default
data: # [Required] ConfigMap data - one or more key-value pairs
WELCOME_MESSAGE: "Hello from KIOPS"
LOG_LEVEL: info
FEATURE_FLAG: "true"
Add this to the Deployment's containers section.
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
envFrom: # [Optional] envFrom + configMapRef - inject all ConfigMap keys as env vars
- configMapRef:
name: app-config # [Required] Name match - ConfigMap to reference
What's next
Step 3 separated configuration, but we still have nowhere to persist data. In Step 4 we add a PVC so data survives Pod restarts.
Step 4: Persistent Data (Adding PVC and a Database)
Prerequisite: Step 3 YAML complete
What you will learn in this step: How to deploy a database with a PVC so that data persists across Pod restarts.
(See Glossary for Secret; ask your IT team or ops team to create it for you.)
Why is this needed
A Pod's filesystem is ephemeral. When a Pod restarts or moves to another node, all files inside the container are gone. That's fine for a static nginx server, but disastrous for a database.
A PVC (PersistentVolumeClaim) is a request for external storage that exists independently of the Pod. Mounting it in the Deployment makes data survive the Pod lifecycle.
Database Deployments also differ from regular apps in two ways:
- replicas: 1: Multiple Pods writing to the same volume can corrupt data.
- strategy: Recreate: The default RollingUpdate spins up a new Pod before terminating the old one, but two Pods trying to claim the same PVC fail.
Recreateterminates the old Pod first and then creates the new one.
This Step assumes a Secret named db-credentials has already been created. Ask your IT team or ops team to create the Secret. Note that this Step introduces four new concepts at once: PVC, the Recreate strategy, volumeMounts / volumes wiring, and Secret references (secretKeyRef).
- PVC size (
spec.resources.requests.storage): Includes a unit such as10Gior100Gi. Expanding after creation depends on what the storage class supports. - Access mode (
accessModes): UseReadWriteOncein almost all cases (read/write from a single node — what most databases use). Switch toReadWriteManyonly when multiple nodes need shared access. - volumes ↔ volumeMounts wiring:
volumes[].namemust equalvolumeMounts[].name, andvolumes[].persistentVolumeClaim.claimNamemust equal the PVC'smetadata.name. - Recreate strategy: For a single-Pod DB backed by a single PVC, use
Recreateto avoid claim conflicts during rollout.
Want more options? → Appendix: PersistentVolumeClaim (PVC), volume section of Appendix: Deployment
- Deleting a PVC carelessly: Deleting just the Deployment removes the Pod but leaves the PVC. However, running
kubectl delete -fon the full YAML deletes the PVC too — and your data is permanently gone. Back up before deletion, and make a habit of usingkubectl delete deploymentdeliberately. - Recreate tradeoff:
Recreatefully terminates the existing Pod before bringing up a new one, causing tens of seconds to several minutes of downtime. It's unavoidable for single-Pod DBs, but never apply it to regular web apps — keep the defaultRollingUpdatefrom Step 1.
KIOPS Configuration Summary
Add one PVC, one Deployment (postgres), and one Service (postgres-svc) to the Step 3 YAML.
| Field | Value | YAML Field |
|---|---|---|
| PVC name | postgres-data | metadata.name (PVC) |
| Storage | 10Gi | spec.resources.requests.storage |
| Storage class | nfs-client | spec.storageClassName |
| Access mode | ReadWriteOnce | spec.accessModes[0] |
| DB Deployment name | postgres | metadata.name (Deployment) |
| Image | postgres:15 | spec.template.spec.containers[0].image |
| Strategy | Recreate | spec.strategy.type |
| Volume mount | /var/lib/postgresql/data | containers[0].volumeMounts[0].mountPath |
Added PVC YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data # [Required] PVC name
namespace: my-app # [Optional] default: default
spec:
accessModes:
- ReadWriteOnce # [Required] Access mode (see new options above)
storageClassName: nfs-client # [Optional] omit to use the cluster default
resources:
requests:
storage: 10Gi # [Required] PVC size (see new options above)
Added Postgres Deployment + Service YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres # [Required] Deployment name
namespace: my-app # [Optional] default: default
spec:
replicas: 1 # [Optional] keep at 1 for a single-instance DB
strategy:
type: Recreate # [Optional] Recreate strategy (see new options above)
selector:
matchLabels:
app: postgres # [Required]
template:
metadata:
labels:
app: postgres # [Required]
spec:
containers:
- name: postgres # [Required]
image: postgres:15 # [Required]
ports:
- containerPort: 5432
# env: write ordinary values (POSTGRES_DB) directly; pull sensitive values (POSTGRES_PASSWORD) from a Secret via valueFrom.secretKeyRef.
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials # [Required] pre-created Secret name (see Prerequisites)
key: password # [Required] key inside the Secret
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage # [Required] volumes ↔ volumeMounts wiring - must match volumes[].name below
mountPath: /var/lib/postgresql/data # [Required] mount path inside container
volumes:
- name: postgres-storage # [Required] volumes ↔ volumeMounts wiring - must match volumeMounts.name
persistentVolumeClaim:
claimName: postgres-data # [Required] volumes ↔ volumeMounts wiring - must match PVC metadata.name
---
apiVersion: v1
kind: Service
metadata:
name: postgres-svc # [Required]
namespace: my-app # [Optional] Default: default
spec:
type: ClusterIP
selector:
app: postgres # [Required] must match Postgres Pod labels
ports:
- port: 5432
targetPort: 5432
Deleting a PVC can permanently destroy its data. Always verify your backups before deleting a PVC in production.
What's next
So far every resource has been long-running. In Step 5 we add a Job — a workload that runs once and exits — to automate DB migrations.
Step 5: One-Time Work (Adding a Job)
Prerequisite: Step 4 YAML complete
What you will learn in this step: How to build a Job that runs a DB migration once before the deployment, with retry semantics.
Why is this needed
Schema migrations, initial seed data, one-time cleanup scripts — these share a few traits:
- They run once and finish (no need to stay alive).
- They must be retried on failure (transient DB issues, etc.).
- Success must be clearly recorded (the Pod's exit code).
If you used a Deployment, the Pod would try to stay running and would be auto-restarted after it exits. A Job leaves the Pod in a terminated state after success and retries up to a configured limit on failure.
- command and args: Override the image's default ENTRYPOINT and run only the migration command.
commandis the executable;argsare its arguments. - restartPolicy (
OnFailure/Never): Jobs cannot useAlways.OnFailurerestarts only the container inside the same Pod;Nevercreates a new Pod each time. - backoffLimit: Retry cap. Once exceeded the Job is marked failed. Too low (0
1) means a transient blip fails the Job immediately; too high (10+) means a real code bug retries forever and burns cluster resources. 35 is the sweet spot. - Reusing envFrom: We reuse the
app-configConfigMap from Step 3. Both the migration and the application share the same DB connection settings.
Want more options? → Appendix: Job
- Watch out: Job and Deployment start together: Migration ordering needs to be guaranteed — see the
Job orderingbox below. backoffLimit3~5 recommended: See the tip box above for details.
KIOPS Configuration Summary
Add one Job to the Step 4 YAML.
| Field | Value | YAML Field |
|---|---|---|
| Job name | db-migrate | metadata.name |
| Image | harbor.example.com/my-app/migrator:v1.0 | spec.template.spec.containers[0].image |
| Command | ["python", "manage.py", "migrate"] | containers[0].command + args |
envFrom | app-config + db-credentials | containers[0].envFrom[] |
backoffLimit | 3 | spec.backoffLimit |
restartPolicy | OnFailure | spec.template.spec.restartPolicy |
Added Job YAML
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate # [Required] Job name
namespace: my-app # [Optional] default: default
spec:
backoffLimit: 3 # [Optional] backoffLimit (see new options above) - default: 6
template:
spec:
restartPolicy: OnFailure # [Required] restartPolicy (see new options above)
containers:
- name: migrator # [Required] container name
image: harbor.example.com/my-app/migrator:v1.0 # [Required] image
command: # [Optional] command and args - override container ENTRYPOINT
- python
args: # [Optional] command and args - command arguments
- manage.py
- migrate
envFrom: # [Optional] Reusing envFrom - bulk inject ConfigMap/Secret
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
Jobs are created at the same time as Deployments, so the application may start before the migration finishes. Mitigate this in one of two ways.
- Add DB connection retry logic to your application.
- Add initContainers in the application Pod that wait for the migration to complete.
What's next
You've finished all five steps. You now understand a complete deployment that combines a static site, an external domain, externalized configuration, persistent data, and one-time work. For deeper details, browse the Appendix below.
Appendix: Kind Reference
A reference section for options not covered in the walkthrough. Look up only what you need.
Deployment
(First introduced in Step 1)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | Deployment resource name |
metadata.namespace | Optional | default | Target namespace |
spec.replicas | Optional | 1 | Pod count. Use 3+ in production |
spec.selector.matchLabels | Required | - | Labels of managed Pods |
spec.template.metadata.labels | Required | - | Labels applied to Pods |
spec.template.spec.containers[].name | Required | - | Container identifier |
spec.template.spec.containers[].image | Required | - | Container image |
spec.template.spec.containers[].ports[].containerPort | Optional | - | Port the container listens on |
spec.template.spec.containers[].imagePullPolicy | Optional | Always | Always, IfNotPresent, Never |
spec.strategy.type | Optional | RollingUpdate | RollingUpdate or Recreate |
spec.strategy.rollingUpdate.maxSurge | Optional | 1 | Extra Pods allowed during update |
spec.strategy.rollingUpdate.maxUnavailable | Optional | 0 | Pods that can be unavailable during update |
Resource limits (spec.template.spec.containers[].resources)
requests says "I need at least this much to work" (reservation); limits says "if I go above this, throttle me" (upper bound). Like a hotel room's minimum vs maximum square footage.
| Option | Required/Optional | Description |
|---|---|---|
requests.cpu / requests.memory | Optional | Guaranteed minimum used for scheduling (e.g. 100m, 128Mi) |
limits.cpu / limits.memory | Optional | Upper bound. Throttled / OOM-killed when exceeded (e.g. 500m, 512Mi) |
Health checks (livenessProbe, readinessProbe)
| Option | Required/Optional | Description |
|---|---|---|
httpGet.path | Required | Health check path (e.g. /health) |
httpGet.port | Required | Health check port |
initialDelaySeconds | Optional | Wait time before the first probe |
periodSeconds | Optional | Probe interval |
timeoutSeconds | Optional | Response timeout |
failureThreshold | Optional | Consecutive failures tolerated |
Deployment strategies are RollingUpdate (zero-downtime gradual replacement) and Recreate (terminate all, then recreate). Blue-Green is not supported directly by KIOPS.
Node placement control (spec.template.spec)
Use this when you want Pods to land only on specific nodes. The Node placement section of the KIOPS deployment form auto-collects the Label/Taint values of the cluster nodes and suggests them as input candidates; your selections are generated into the YAML fields below.
| Option | Required/Optional | Description |
|---|---|---|
nodeSelector | Optional | Schedule only onto nodes whose Labels exactly match (e.g. disktype: ssd) |
affinity.nodeAffinity | Optional | Schedule using Label expressions (In/NotIn/Exists/DoesNotExist). More flexible than nodeSelector |
tolerations | Optional | Tolerate a node's Taint so the Pod is allowed there. key/operator/value/effect (NoSchedule/PreferNoSchedule/NoExecute) |
To place a Pod on a tainted node, it must have a Toleration that tolerates that Taint. A Toleration only grants permission to land there; to force placement, combine it with nodeSelector or nodeAffinity.
Service
(First introduced in Step 1)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | Service name |
spec.type | Optional | ClusterIP | ClusterIP, NodePort, LoadBalancer |
spec.selector | Required | - | Labels of Pods receiving traffic |
spec.ports[].port | Required | - | Port the Service listens on |
spec.ports[].targetPort | Required | - | Container port traffic is forwarded to |
spec.ports[].protocol | Optional | TCP | TCP, UDP, SCTP |
spec.ports[].nodePort | Optional | auto-assigned | Node port when type: NodePort |
Type-specific scenarios
| Type | Use case |
|---|---|
ClusterIP | In-cluster traffic, paired with Ingress (most common) |
NodePort | Reached from an external reverse proxy (external Nginx, etc.) via a node port |
LoadBalancer | Cloud environment, external IP assigned directly |
Ingress
(First introduced in Step 2)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | Ingress name |
spec.ingressClassName | Optional | cluster default | Selects the Ingress Controller |
spec.rules[].host | Required | - | Domain to route |
spec.rules[].http.paths[].path | Required | - | Path to match |
spec.rules[].http.paths[].pathType | Required | - | Prefix or Exact |
spec.rules[].http.paths[].backend.service.name | Required | - | Target Service name |
spec.rules[].http.paths[].backend.service.port.number | Required | - | Target Service port |
spec.tls[].secretName | Optional | - | Secret holding the HTTPS certificate |
spec.tls[].hosts | Optional | - | Domains covered by the TLS cert |
Ingress Class choices (6 options)
| Class | Description |
|---|---|
traefik | Auto certificates, middleware chains. KIOPS's recommended default |
nginx | Community project. EOL scheduled for March 2026 — migration recommended |
haproxy | High-performance load balancing. Suited for enterprise environments |
kong | API Gateway integration with plugin extensibility |
contour | CNCF project built on the Envoy proxy |
alb | AWS Application Load Balancer (AWS only) |
When multiple paths could match, the first item in the paths array wins. Put /api before / so API calls don't hit the static frontend.
ConfigMap
(First introduced in Step 3)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | ConfigMap name |
metadata.namespace | Optional | default | Target namespace |
data | Required | - | Key-value pairs. All values are strings |
How to consume in a Deployment
envFrom: configMapRef: Inject all keys as env vars (simplest)env[].valueFrom.configMapKeyRef: Pick a specific key and rename it as a different env varvolumes[].configMap: Mount as files (when you need a config file on disk)
Use a Secret instead of a ConfigMap for sensitive data; the usage pattern is identical (secretRef, secretKeyRef).
PersistentVolumeClaim (PVC)
(First introduced in Step 4)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | PVC name |
spec.accessModes | Required | - | Array of access modes |
spec.resources.requests.storage | Required | - | Requested size (e.g. 10Gi) |
spec.storageClassName | Optional | cluster default | Storage provisioner to use |
Storage class examples: nfs-client, local-path, longhorn, ceph-rbd, default. Pick from what's installed on your cluster.
Access modes (3 options)
| Mode | Description | Use case |
|---|---|---|
ReadWriteOnce | Read/write from a single node | Most databases and general apps (default) |
ReadWriteMany | Read/write from multiple nodes concurrently | Shared storage such as NFS |
ReadOnlyMany | Read-only from multiple nodes | Sharing static config files |
Job
(First introduced in Step 5)
| Option | Required/Optional | Default | Description |
|---|---|---|---|
metadata.name | Required | - | Job name |
spec.template.spec.containers[].image | Required | - | Image to run |
spec.template.spec.containers[].command | Optional | - | Override ENTRYPOINT |
spec.template.spec.containers[].args | Optional | - | Arguments to command |
spec.template.spec.restartPolicy | Required | - | OnFailure or Never |
spec.backoffLimit | Optional | 6 | Retries on failure |
spec.activeDeadlineSeconds | Optional | - | Maximum total runtime in seconds |
Environment variable injection options
| Method | Description |
|---|---|
env (individual) | Define vars one by one. Can reference a single key of a ConfigMap/Secret |
envFrom: configMapRef | Inject all keys of a ConfigMap as env vars |
envFrom: secretRef | Inject all keys of a Secret as env vars |
Jobs are for one-time work — DB migrations, seed data, cleanup scripts. For recurring scheduled work, use a CronJob.
What's Next
Once you've finished the guide, take the following path.
- Run a real deployment → K8s Deployment
- Scale automatically when traffic grows → HPA Autoscaling
- Enable HTTPS → Domain/SSL Setup
- Recover from a bad release → Rollback
Related Guides
- K8s Deployment - Deploy with the generated YAML
- Rollback - Restore to a previous version when something goes wrong
- Domain/SSL Setup - Custom domain and HTTPS configuration
- HPA Autoscaling - Automatic Pod scaling based on traffic