Deploy a Custom Workload
Tutorial for deploying your own applications to LoKO.
Overview
Section titled “Overview”This tutorial covers:
- Creating a custom workload definition
- Deploying from custom Helm charts
- Using your own Docker images
- Configuring ingress and services
- Adding to the local catalog
Time: 15 minutes
Prerequisites:
- LoKO environment running
- Basic Kubernetes/Helm knowledge
- Docker image (or use example)
Scenario
Section titled “Scenario”We’ll deploy a custom web application with:
- Frontend (Nginx serving static files)
- Backend API (Node.js/Express)
- Database connection (PostgreSQL)
- Ingress for HTTP access
Method 1: Quick Deployment (No Helm Chart)
Section titled “Method 1: Quick Deployment (No Helm Chart)”Step 1: Create Docker Image
Section titled “Step 1: Create Docker Image”app.js (Simple Express API):
const express = require('express')const app = express()
app.get('/api/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date() })})
app.get('/api/hello', (req, res) => { res.json({ message: 'Hello from LoKO!' })})
app.listen(3000, () => console.log('API listening on port 3000'))Dockerfile:
FROM node:18-alpineWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .EXPOSE 3000CMD ["node", "app.js"]Build and push:
# Builddocker build -t my-api:latest .
# Tag for local registrydocker tag my-api:latest cr.dev.me:5000/my-api:latest
# Push to LoKO registrydocker push cr.dev.me:5000/my-api:latestStep 2: Create Kubernetes Manifests
Section titled “Step 2: Create Kubernetes Manifests”deployment.yaml:
apiVersion: apps/v1kind: Deploymentmetadata: name: my-api namespace: defaultspec: replicas: 2 selector: matchLabels: app: my-api template: metadata: labels: app: my-api spec: containers: - name: api image: cr.dev.me:5000/my-api:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: development---apiVersion: v1kind: Servicemetadata: name: my-api namespace: defaultspec: selector: app: my-api ports: - port: 3000 targetPort: 3000---apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-api namespace: defaultspec: rules: - host: api.dev.me http: paths: - path: / pathType: Prefix backend: service: name: my-api port: number: 3000Step 3: Deploy
Section titled “Step 3: Deploy”kubectl apply -f deployment.yamlStep 4: Access
Section titled “Step 4: Access”# Wait for podskubectl get pods
# Test APIcurl http://api.dev.me/api/healthcurl http://api.dev.me/api/helloMethod 2: Using Helm Chart
Section titled “Method 2: Using Helm Chart”Step 1: Create Helm Chart
Section titled “Step 1: Create Helm Chart”# Create chart structurehelm create my-app
cd my-appEdit values.yaml:
replicaCount: 2
image: repository: cr.dev.me:5000/my-api tag: latest pullPolicy: Always
service: type: ClusterIP port: 3000
ingress: enabled: true className: traefik hosts: - host: myapp.dev.me paths: - path: / pathType: Prefix tls: []
resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128Mi
env: - name: NODE_ENV value: development - name: DB_HOST value: postgres.dev.me - name: DB_PORT value: "5432"Edit templates/deployment.yaml: Add environment variables from values:
{{- if .Values.env }}env:{{- toYaml .Values.env | nindent 12 }}{{- end }}Step 2: Package Chart
Section titled “Step 2: Package Chart”# Package charthelm package .
# Output: my-app-0.1.0.tgzStep 3: Deploy with Helm
Section titled “Step 3: Deploy with Helm”# Installhelm install my-app ./my-app-0.1.0.tgz -n default
# Upgradehelm upgrade my-app ./my-app-0.1.0.tgz -n default
# Check statushelm status my-app -n defaultStep 4: Access
Section titled “Step 4: Access”curl http://myapp.dev.me/api/healthMethod 3: Add to LoKO Catalog
Section titled “Method 3: Add to LoKO Catalog”Step 1: Create Catalog Entry
Section titled “Step 1: Create Catalog Entry”Edit ~/.loko/catalog/catalog.yaml:
workloads: my-app: category: "application" description: "My custom application" chart: repo: "local" # Or your Helm repo name: "my-app" version: "0.1.0" defaults: namespace: "default" ports: [] # No TCP ports needed (HTTP only) presets: replicaCount: 2 image: repository: cr.dev.me:5000/my-api tag: latest ingress: enabled: true hosts: - host: myapp.dev.me paths: - path: / pathType: PrefixStep 2: Add Helm Repository (Optional)
Section titled “Step 2: Add Helm Repository (Optional)”If using remote Helm repository:
helm-repositories: - name: "my-repo" url: "https://charts.example.com"Step 3: Use LoKO Commands
Section titled “Step 3: Use LoKO Commands”Now you can manage it like built-in workloads:
# Add to environmentloko workloads add my-app
# Deployloko workloads deploy my-app
# Check statusloko workloads list
# Removeloko workloads remove my-app --nowAdvanced: Multi-Container Application
Section titled “Advanced: Multi-Container Application”Application Stack
Section titled “Application Stack”- Frontend: React app (Nginx)
- Backend: Node.js API
- Database: PostgreSQL
- Cache: Redis
Step 1: Deploy Dependencies
Section titled “Step 1: Deploy Dependencies”# Deploy database and cacheloko workloads add postgres --nowloko workloads add valkey --nowStep 2: Backend Deployment
Section titled “Step 2: Backend Deployment”values-backend.yaml:
replicaCount: 2image: repository: cr.dev.me:5000/my-backend tag: latestservice: port: 3000env: - name: DB_HOST value: postgres.dev.me - name: DB_PORT value: "5432" - name: DB_NAME value: myapp - name: REDIS_HOST value: valkey.dev.me - name: REDIS_PORT value: "6379"envFrom: - secretRef: name: backend-secrets # Create manually or auto-generateingress: enabled: true hosts: - host: api.dev.me paths: - path: /api pathType: PrefixStep 3: Frontend Deployment
Section titled “Step 3: Frontend Deployment”values-frontend.yaml:
replicaCount: 2image: repository: cr.dev.me:5000/my-frontend tag: latestservice: port: 80env: - name: API_URL value: http://api.dev.meingress: enabled: true hosts: - host: app.dev.me paths: - path: / pathType: PrefixStep 4: Deploy Stack
Section titled “Step 4: Deploy Stack”# Deploy backendhelm install backend ./my-backend -f values-backend.yaml
# Deploy frontendhelm install frontend ./my-frontend -f values-frontend.yaml
# Check statuskubectl get podskubectl get ingressStep 5: Access
Section titled “Step 5: Access”- Frontend: http://app.dev.me
- Backend API: http://api.dev.me/api
- Database: postgres.dev.me:5432
- Cache: valkey.dev.me:6379
Configuration Examples
Section titled “Configuration Examples”Environment Variables
Section titled “Environment Variables”env: - name: LOG_LEVEL value: debug - name: PORT value: "3000" - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: passwordSecrets
Section titled “Secrets”# Create secretkubectl create secret generic app-secrets \ --from-literal=api-key=abc123 \ --from-literal=db-password=secret \ -n default
# Reference in deploymentenvFrom: - secretRef: name: app-secretsConfig Maps
Section titled “Config Maps”# Create config mapkubectl create configmap app-config \ --from-file=config.json \ -n default
# Mount in deploymentvolumeMounts: - name: config mountPath: /etc/configvolumes: - name: config configMap: name: app-configPersistent Storage
Section titled “Persistent Storage”persistence: enabled: true storageClass: standard size: 10Gi mountPath: /dataTroubleshooting
Section titled “Troubleshooting”Image pull fails
Section titled “Image pull fails”# Check registrykubectl get pods -n loko-components | grep registry
# Check image existscurl http://cr.dev.me:5000/v2/_catalog
# Check image in registrydocker pull cr.dev.me:5000/my-api:latestIngress not working
Section titled “Ingress not working”# Check ingresskubectl get ingress
# Check Traefikkubectl get pods -n loko-components | grep traefik
# Test internal servicekubectl run -it test --rm --image=alpine -- shwget -O- http://my-api.default.svc.cluster.local:3000/api/healthPods not starting
Section titled “Pods not starting”# Check podskubectl get pods
# Check eventskubectl describe pod <pod-name>
# Check logskubectl logs <pod-name>
# Check resourceskubectl top nodeskubectl top podsBest Practices
Section titled “Best Practices”1. Use Health Checks
Section titled “1. Use Health Checks”livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10
readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 52. Resource Limits
Section titled “2. Resource Limits”resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi3. Multiple Replicas
Section titled “3. Multiple Replicas”replicaCount: 2 # High availability4. Use Namespaces
Section titled “4. Use Namespaces”# Create namespacekubectl create namespace my-app
# Deploy to namespacehelm install my-app ./chart -n my-app5. Version Images
Section titled “5. Version Images”image: tag: "1.0.0" # Not "latest"Next Steps
Section titled “Next Steps”- Multi-Node Setup - Scale your cluster
- Workload Management - Manage workloads
- Registry Guide - Use local registry
- Configuration - Advanced config
See Also
Section titled “See Also”- Helm Documentation - Helm charts
- Kubernetes Documentation - Kubernetes concepts
- Docker Documentation - Docker images