Skip to main content

K8s YAML Generation Guide

On KIWI's [Service Management] page, you can generate Kubernetes deployment YAML files automatically by selecting options in a form, without writing YAML manually. This guide explains the available resource kinds and their options, along with practical examples of how to configure YAML for real projects.

No need to write YAML manually

KIWI automatically generates standard Kubernetes YAML based on the options you enter. You can review the generated YAML in the preview, and edit it directly if needed.

Accessing the YAML Generation Page

  1. Go to the [Service Management] page and select a service.
  2. Click the Deploy stage in the pipeline.
  3. Select K8s deployment in Deploy Environment Settings to see the YAML generation form.

Part 1: Options by Kind

KIWI supports generating 6 types of Kubernetes resources. Combine the Kinds you need based on your project structure.

KindPurposeRequired
DeploymentApplication Pod deployment✅ Almost always
ServiceNetwork access to Pods✅ Almost always
IngressExternal domain routingOptional
ConfigMapSeparated configuration managementOptional
PVCPersistent storageOptional
JobOne-time tasks (migration, etc.)Optional
Choosing a Network Exposure Method
  • Ingress Controller (recommended): Set Service to ClusterIP and create an Ingress. Best for domain-based routing and the most common approach.
  • NodePort (external Nginx): Access via NodePort through an external reverse proxy. No Ingress needed.
  • LoadBalancer (external IP): Assigns an external IP directly. Mainly used in cloud environments. No Ingress needed.

Deployment

Defines how application Pods are deployed.

Why do I need a Deployment?

Running containers directly provides no automatic recovery on failure. A Deployment maintains your desired number of Pods, automatically restarts on failure, and handles zero-downtime updates when deploying new versions.

Basic Options

OptionRequiredDefaultDescription
Name-Deployment resource name. Example: my-app
NamespacedefaultTarget namespace for deployment
Replicas1Number of Pods to run simultaneously. 3+ recommended for production
imagePullPolicyAlwaysImage pull policy. Choose from Always, IfNotPresent, Never
Labels-Key-value pairs matching Service selectors. Example: app: my-app

Containers (at least 1 required)

Defines the containers to run within the Pod.

OptionRequiredDefaultDescription
Name-Container identifier. Example: frontend
Image-Container image address. Select from build list or enter manually
Port80Port the container listens on
Environment Variables-Supports direct input (key-value), secretKeyRef, configMapKeyRef
Command / Args-Command and arguments to run at startup. Example: ["node", "server.js"]
Volume Mounts-Mount a PVC to a container path. Specify name, mountPath, claimName

Deployment Strategy

Determines how Pods are replaced during version updates.

StrategyDescriptionAdditional Options
RollingUpdate (default)Gradual replacement. Suitable for zero-downtime deploymentmaxSurge (default 1), maxUnavailable (default 0)
RecreateTerminates all Pods before creating new ones. May cause downtime-

Resources (optional)

Configures CPU and memory allocation for the Pod.

TypeDescriptionExample
requestsMinimum guaranteed amount. Used for schedulingcpu: 100m, memory: 128Mi
limitsMaximum ceiling. Throttled when exceededcpu: 500m, memory: 512Mi

Health Checks (optional)

Configures probes to check Pod health.

ProbeRoleAction on Failure
Liveness ProbeChecks if the Pod is aliveRestarts the Pod
Readiness ProbeChecks if the Pod is ready for trafficBlocks traffic

Options for each probe:

OptionDescription
pathHealth check request path. Example: /health
portHealth check request port
initialDelaySecondsWait time (seconds) before the first check after Pod starts
periodSecondsCheck interval (seconds)
timeoutSecondsResponse timeout (seconds)
failureThresholdNumber of consecutive failures allowed

Service

Defines network endpoints for accessing Pods.

Why do I need a Service?

Pod IPs change every time they restart. A Service provides a stable address so other Pods and external clients can reliably access your application.

Basic Options

OptionRequiredDefaultDescription
Name-Service resource name. Example: my-app-svc
NamespacedefaultTarget namespace for deployment
Selector-Labels to select which Pods receive traffic. Must match Deployment labels

Type

Choose based on the network exposure method.

TypeDescriptionUse Case
ClusterIP (default)Accessible only within the clusterUsed with Ingress
NodePortAccessible externally through a specific node portUsed with external reverse proxies (Nginx, etc.)
LoadBalancerAssigns an external IP for direct exposurePrimarily used in cloud environments

Ports (at least 1)

OptionRequiredDefaultDescription
name-Port identifier. Example: http
protocolTCPProtocol
port-Port the Service listens on
targetPort-Container port to forward traffic to
nodePort-Port opened on the node for NodePort type

Ingress

Defines rules for routing external traffic to internal Services.

Why do I need an Ingress?

Services alone cannot handle domain-based routing or HTTPS. An Ingress routes requests coming to domains like app.example.com to the appropriate Services based on path, and also manages TLS certificates.

Basic Options

OptionRequiredDefaultDescription
Name-Ingress resource name. Example: my-app-ingress
NamespacedefaultTarget namespace for deployment
Annotations-Key-value pairs to control Ingress Controller behavior

Ingress Class

Select the Ingress Controller to use.

Ingress ClassDescription
traefik (recommended)Supports automatic certificates and middleware chains
nginxCommunity project. EOL scheduled for March 2026; migration recommended
haproxyHigh-performance load balancing. Suitable for enterprise environments
kongAPI Gateway integration with plugin extensions
contourEnvoy proxy-based CNCF project
albAWS Application Load Balancer. For AWS cloud environments only

Rules (at least 1)

Defines routing rules per domain.

OptionDescription
hostDomain name. Example: app.example.com
pathsPer-path routing. Specify path, pathType (Prefix), serviceName, servicePort

TLS (optional)

Enables HTTPS. Specify hosts (domain list) and secretName (certificate Secret).


ConfigMap

Stores application configuration values as key-value pairs.

Why use ConfigMap?

When you enter environment variables directly in a Deployment, the values are exposed in the YAML. By separating them into a ConfigMap, you can swap configurations per environment and share the same settings across multiple Deployments. For sensitive information like passwords or API keys, use Secret instead of ConfigMap.

OptionRequiredDefaultDescription
Name-ConfigMap resource name. Example: app-config
NamespacedefaultTarget namespace for deployment
Data-Key-value pairs. Example: DATABASE_HOST: postgres, LOG_LEVEL: info
Labels-Key-value pairs

PersistentVolumeClaim (PVC)

Requests persistent storage for Pods. Data is preserved even when Pods restart or are deleted.

Why do I need a PVC?

Pod file systems are ephemeral — data is lost when a Pod restarts. Data requiring permanent retention, such as databases or uploaded files, must be stored on external storage via a PVC.

OptionRequiredDefaultDescription
Name-PVC resource name. Example: app-data-pvc
NamespacedefaultTarget namespace for deployment
Storage Size10GiRequested disk size
Storage Classnfs-clientStorage provisioner to use

Storage Class

Options include nfs-client, local-path, longhorn, ceph-rbd, and default.

Access Modes

ModeDescriptionUse Case
ReadWriteOnce (default)Read/write from a single nodeSuitable for most cases
ReadWriteManySimultaneous read/write from multiple nodesRequires shared storage like NFS
ReadOnlyManyRead-only from multiple nodesSharing config files, etc.
PVC Deletion Warning

Deleting a PVC may permanently destroy the associated data. Always verify backups before deletion.


Job

Runs one-time or batch tasks. The Pod terminates after the task completes.

When to use a Job?

Use Jobs for tasks that run once and finish, such as DB migrations, initial data seeding, or one-time script execution. Unlike Deployments, the Pod automatically terminates after the task completes.

Basic Options

OptionRequiredDefaultDescription
Name-Job resource name. Example: db-migrate-job
NamespacedefaultTarget namespace for deployment
Image-Container image to run in the Job
Command / Args-Command and arguments. Example: ["python"], ["manage.py", "migrate"]
Environment Variables-Enter directly as key-value pairs
backoffLimit3Retry count on failure. Job fails after exceeding this count

Job Type

TypeDescription
DB MigrationFor database schema changes
InitializationFor initial data setup, seed data insertion, etc.
CustomUser-defined one-time tasks

envFrom (optional)

Injects an entire ConfigMap or Secret as environment variables.

Reference TypeDescription
configMapRefLoads all keys from a ConfigMap as environment variables
secretRefLoads all keys from a Secret as environment variables

restartPolicy

PolicyDescription
OnFailure (default)Restarts the container within the same Pod on failure
NeverCreates a new Pod instead of restarting on failure

Part 2: YAML Generation Examples

Example 1: Basic Web Service (Frontend + Backend + Database)

A typical web service composed of React frontend + Node.js API backend + PostgreSQL database.

Service Architecture

User → Ingress (app.example.com)
├─ / → frontend-svc(:80) → frontend Pod (React/Nginx)
└─ /api → backend-svc(:3000) → backend Pod (Node.js)

postgres-svc(:5432) → postgres Pod

Generated Resources

  • 3 Deployments (frontend, backend, postgres)
  • 3 Services (frontend-svc, backend-svc, postgres-svc)
  • 1 Ingress (app-ingress)

File Structure

k8s/
├── deployment.yaml ← frontend + backend + postgres Deployments
├── service.yaml ← frontend-svc + backend-svc + postgres-svc
└── ingress.yaml ← app-ingress

YAML is generated with separate files per Kind. When there are multiple resources of the same Kind, they are combined into one file separated by ---.

Frontend — Deployment & Service

KIWI settings summary:

OptionValue
Namefrontend
Namespacemy-web-app
Replicas2
Imageharbor.example.com/my-project/frontend:v1.0
Port80
StrategyRollingUpdate (maxSurge: 1, maxUnavailable: 0)
Resourcesrequests: 100m/128Mi, limits: 300m/256Mi
Service TypeClusterIP
  • The built React app is served via Nginx, so port 80 is used.
  • Frontend serves static files, so resource allocation is kept small.
View generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: my-web-app
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: harbor.example.com/my-project/frontend:v1.0
ports:
- containerPort: 80
imagePullPolicy: Always
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
namespace: my-web-app
spec:
type: ClusterIP
selector:
app: frontend
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80

Backend — Deployment & Service

KIWI settings summary:

OptionValue
Namebackend
Namespacemy-web-app
Replicas2
Imageharbor.example.com/my-project/backend:v1.0
Port3000
StrategyRollingUpdate (maxSurge: 1, maxUnavailable: 0)
Env VarsDATABASE_HOST, DATABASE_PORT, DATABASE_NAME + Secret ref
Resourcesrequests: 200m/256Mi, limits: 500m/512Mi
Health ChecksLiveness + Readiness (/health:3000)
Service TypeClusterIP
  • DATABASE_HOST uses the postgres Service name. It is automatically resolved via DNS within the same namespace.
  • The password is fetched from a Secret via secretKeyRef, avoiding direct exposure in YAML.
  • Health checks are configured to automatically detect unhealthy Pods.
View generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: my-web-app
labels:
app: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: harbor.example.com/my-project/backend:v1.0
ports:
- containerPort: 3000
imagePullPolicy: Always
env:
- name: DATABASE_HOST
value: postgres-svc
- name: DATABASE_PORT
value: "5432"
- name: DATABASE_NAME
value: myapp
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
namespace: my-web-app
spec:
type: ClusterIP
selector:
app: backend
ports:
- name: http
protocol: TCP
port: 3000
targetPort: 3000

Database — Deployment & Service

KIWI settings summary:

OptionValue
Namepostgres
Namespacemy-web-app
Replicas1
Imagepostgres:15
Port5432
StrategyRecreate
imagePullPolicyIfNotPresent
Env VarsPOSTGRES_DB + Secret ref
Service TypeClusterIP
  • Database is set to replicas 1 for data consistency with a single instance.
  • Strategy is set to Recreate because multiple Pods accessing the same volume simultaneously can cause issues.
  • Uses ClusterIP since external exposure is not needed.
View generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-web-app
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
strategy:
type: Recreate
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
imagePullPolicy: IfNotPresent
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
---
apiVersion: v1
kind: Service
metadata:
name: postgres-svc
namespace: my-web-app
spec:
type: ClusterIP
selector:
app: postgres
ports:
- name: tcp
protocol: TCP
port: 5432
targetPort: 5432

Ingress

KIWI settings summary:

OptionValue
Nameapp-ingress
Namespacemy-web-app
Ingress Classtraefik
Hostapp.example.com
TLSapp-tls-secret
Routing/api → backend-svc:3000, / → frontend-svc:80
  • The /api path is defined before / because Ingress matches in order, preventing API requests from going to the frontend.
  • TLS is configured to enable HTTPS.
View generated YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: my-web-app
spec:
ingressClassName: traefik
tls:
- secretName: app-tls-secret
hosts:
- app.example.com
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-svc
port:
number: 3000
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80

Example 2: Advanced Project (Volume + Job + ConfigMap)

A project composed of Spring Boot API + PostgreSQL with ConfigMap for configuration separation, PVC for persistent data storage, and Job for DB migration.

Service Architecture

[ConfigMap: app-config]           ← Environment settings (DB connection, log level, etc.)

├── envFrom ──→ [Job: db-migrate] ← Runs migration then exits

└── envFrom ──→ [Deployment: api-server]

└──→ [Service: api-svc] → [Ingress: api-ingress]

[PVC: postgres-data] ← 10Gi persistent storage

└── mount ──→ [Deployment: postgres]

└──→ [Service: postgres-svc]

Generated Resources

  • 1 ConfigMap (app-config)
  • 1 PVC (postgres-data)
  • 1 Job (db-migrate)
  • 2 Deployments (api-server, postgres)
  • 2 Services (api-svc, postgres-svc)
  • 1 Ingress (api-ingress)

File Structure

k8s/
├── configmap.yaml ← app-config
├── pvc.yaml ← postgres-data
├── job.yaml ← db-migrate
├── deployment.yaml ← api-server + postgres Deployments
├── service.yaml ← api-svc + postgres-svc
└── ingress.yaml ← api-ingress

ConfigMap

KIWI settings summary:

OptionValue
Nameapp-config
Namespacemy-api-project
DataDATABASE_HOST, DATABASE_PORT, DATABASE_NAME, LOG_LEVEL, SERVER_PORT, SPRING_PROFILES_ACTIVE
  • Manages DB connection info, log level, and server settings in one place.
  • Both Deployment and Job reference the same ConfigMap, preventing configuration mismatches.
View generated YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: my-api-project
labels:
app: app-config
data:
DATABASE_HOST: postgres-svc
DATABASE_PORT: "5432"
DATABASE_NAME: myapi
LOG_LEVEL: info
SERVER_PORT: "8080"
SPRING_PROFILES_ACTIVE: production

PersistentVolumeClaim

KIWI settings summary:

OptionValue
Namepostgres-data
Namespacemy-api-project
Size10Gi
Storage Classnfs-client
Access ModeReadWriteOnce
  • Set to ReadWriteOnce since the database only needs access from a single node.
  • Using nfs-client stores data on an NFS server, preserving data even during node failures.
View generated YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: my-api-project
labels:
app: postgres-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-client
resources:
requests:
storage: 10Gi

Job — DB Migration

KIWI settings summary:

OptionValue
Namedb-migrate
Namespacemy-api-project
Imageharbor.example.com/my-project/api-server:v1.0
Commandjava -jar app.jar --spring.flyway.enabled=true --spring.main.web-application-type=none
envFromapp-config (ConfigMap) + db-credentials (Secret)
backoffLimit3
restartPolicyOnFailure
  • Uses the same image as the application but only runs Flyway migration without starting the web server.
  • envFrom injects the entire ConfigMap and Secret as environment variables, eliminating the need to map individual keys.
  • backoffLimit: 3 handles transient failures like DB connection issues.
Job Execution Order

Jobs are created simultaneously with Deployments. The application may start before migration completes, so the application should include DB connection retry logic. Alternatively, you can use initContainers to wait for migration completion.

View generated YAML
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-api-project
labels:
app.kubernetes.io/part-of: db-migrate
app.kubernetes.io/component: db-migrate
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: db-migrate
image: harbor.example.com/my-project/api-server:v1.0
command:
- java
args:
- -jar
- app.jar
- --spring.flyway.enabled=true
- --spring.main.web-application-type=none
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials

API Server — Deployment & Service

KIWI settings summary:

OptionValue
Nameapi-server
Namespacemy-api-project
Replicas3
Imageharbor.example.com/my-project/api-server:v1.0
Port8080
StrategyRollingUpdate (maxSurge: 1, maxUnavailable: 0)
envFromapp-config (ConfigMap) + db-credentials (Secret)
Resourcesrequests: 300m/512Mi, limits: 1/1Gi
Health ChecksLiveness + Readiness (/actuator/health:8080)
Service TypeClusterIP
  • envFrom injects the ConfigMap and Secret in bulk, sharing the same configuration with the Job.
  • Uses Spring Boot's Actuator health check endpoint (/actuator/health).
  • JVM-based applications start slowly, so initialDelaySeconds is set generously.
View generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: my-api-project
labels:
app: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api-server
image: harbor.example.com/my-project/api-server:v1.0
ports:
- containerPort: 8080
imagePullPolicy: Always
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
resources:
requests:
cpu: 300m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: api-svc
namespace: my-api-project
spec:
type: ClusterIP
selector:
app: api-server
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080

Database — Deployment & Service (with PVC)

KIWI settings summary:

OptionValue
Namepostgres
Namespacemy-api-project
Replicas1
Imagepostgres:15
Port5432
StrategyRecreate
Env VarsPOSTGRES_DB, PGDATA + Secret ref
Volume Mountpostgres-data PVC → /var/lib/postgresql/data
Service TypeClusterIP
  • volumeMounts and volumes connect the PVC. Data at /var/lib/postgresql/data is preserved even when the Pod restarts.
  • The PGDATA environment variable points to a subdirectory (pgdata). This prevents PostgreSQL initialization failure when hidden files exist at the NFS mount root directory.
View generated YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-api-project
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
strategy:
type: Recreate
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
imagePullPolicy: IfNotPresent
env:
- name: POSTGRES_DB
value: myapi
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-data
---
apiVersion: v1
kind: Service
metadata:
name: postgres-svc
namespace: my-api-project
spec:
type: ClusterIP
selector:
app: postgres
ports:
- name: tcp
protocol: TCP
port: 5432
targetPort: 5432

Ingress

KIWI settings summary:

OptionValue
Nameapi-ingress
Namespacemy-api-project
Ingress Classtraefik
Hostapi.example.com
TLSapi-tls-secret
Routing/ → api-svc:8080
View generated YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: my-api-project
spec:
ingressClassName: traefik
tls:
- secretName: api-tls-secret
hosts:
- api.example.com
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 8080