1. What is Cloud Foundry & Why Use It
- Cloud-native PaaS (Platform as a Service) — open source, originally by VMware/Pivotal, now Cloud Foundry Foundation
- Key philosophy: “Here’s my code, run it” — developer doesn’t care about containers, VMs, networking
- Distributions: Open Source CF (cf-deployment), VMware Tanzu (TAS), SAP BTP, IBM Cloud Foundry, Swisscom Application Cloud
- CF on Kubernetes: KubeCF, Korifi (next-gen CF on K8s)
CF vs Kubernetes Comparison
| Aspect | Cloud Foundry | Kubernetes |
|---|---|---|
| Abstraction level | Application (PaaS) | Container (CaaS) |
| Developer experience | cf push (seconds) | Dockerfile + YAML + kubectl (minutes-hours) |
| Learning curve | Low — focus on app, not infra | High — must understand pods, services, ingress, etc. |
| Buildpacks | Built-in (auto-detect language) | Need to add (Paketo/Cloud Native Buildpacks) |
| Service binding | Native marketplace (cf create-service) | Operators/Helm charts, manual config |
| Multi-tenancy | Built-in (Orgs → Spaces → Roles) | Namespaces + RBAC (manual setup) |
| Routing | Built-in (Gorouter), automatic | Ingress controller needed (NGINX/Traefik) |
| Scaling | cf scale app -i 5 | HPA, VPA, manual replicas |
| Health management | Auto-restarts, crash recovery | Liveness/readiness probes (you configure) |
| Logging | cf logs (Loggregator) built-in | Need Loki/EFK stack |
| Metrics | Built-in (Firehose/Nozzle) | Prometheus + custom setup |
| Service mesh | Built-in routing, no extra | Istio/Linkerd (complex) |
| Best for | App developers, rapid deployment | Infra teams, microservices at scale |
| Overhead | Higher (runs on top of BOSH/K8s) | Lower base, but more DIY |
Architecture Overview
Developer
│
▼ cf push
Cloud Controller (API)
│
├── Diego (Container Runtime)
│ ├── Diego Cell 1 → [App Instance 1]
│ ├── Diego Cell 2 → [App Instance 2]
│ └── Diego Cell 3 → [App Instance 3]
│
├── Gorouter (HTTP Routing)
│ └── Routes: myapp.cf.example.com → App Instances
│
├── Loggregator (Logging)
│ └── cf logs myapp
│
├── Service Broker (Marketplace)
│ ├── MySQL
│ ├── PostgreSQL
│ ├── Redis
│ ├── RabbitMQ
│ ├── S3/Object Storage
│ └── Custom services
│
└── UAA (Authentication & Authorization)
├── Orgs → Spaces → Users → Roles
└── OAuth2 / SAML / LDAP2. cf CLI Essentials
Login & Target
cf login -a https://api.cf.example.com
cf login -a https://api.cf.example.com --sso # SSO login
cf target -o my-org -s my-space
cf orgs # list orgs
cf spaces # list spaces
cf target # show current targetPush & Deploy
cf push myapp # push from current dir (uses manifest.yml)
cf push myapp -p ./build -b nodejs_buildpack # explicit path + buildpack
cf push myapp --docker-image myregistry/myapp:v1 # Docker image
cf push myapp -f manifest-prod.yml # specific manifest
cf push myapp --no-start # push without starting
cf push myapp --strategy rolling # zero-downtime rolling deploy
cf push myapp -i 3 -m 256M -k 1G # 3 instances, 256MB RAM, 1GB diskApp Management
cf apps # list all apps
cf app myapp # app details (instances, memory, routes)
cf start myapp
cf stop myapp
cf restart myapp
cf restage myapp # rebuild droplet (after env/service change)
cf delete myapp -r # delete app + routes
cf rename myapp myapp-v2Scaling
cf scale myapp -i 5 # scale to 5 instances
cf scale myapp -m 512M # change memory
cf scale myapp -k 2G # change disk
cf scale myapp -i 5 -m 512M # both at onceLogs & SSH
cf logs myapp # stream live logs
cf logs myapp --recent # recent logs (dump)
cf events myapp # app events (crashes, restarts, etc.)
cf ssh myapp # SSH into app container
cf ssh myapp -c "cat /app/config.yml" # run command
cf enable-ssh myapp # enable SSH accessEnvironment Variables
cf set-env myapp NODE_ENV production
cf set-env myapp DATABASE_URL "postgres://..."
cf unset-env myapp DEBUG
cf env myapp # show all env vars (system + user)
cf restage myapp # REQUIRED after env change!Routes & Domains
cf routes # list routes
cf map-route myapp example.com --hostname api # api.example.com → myapp
cf unmap-route myapp example.com --hostname old # remove route
cf create-route my-space example.com --hostname new
cf domains # list available domains
cf create-domain my-org custom.com # add custom domain3. manifest.yml — The Deployment Descriptor
---
applications:
- name: myapp
memory: 256M
disk_quota: 1G
instances: 3
buildpacks:
- nodejs_buildpack
path: ./build
command: node server.js
# Health check
health-check-type: http # http, port, process, none
health-check-http-endpoint: /health
health-check-invocation-timeout: 10
timeout: 180 # startup timeout
# Routes
routes:
- route: myapp.example.com
- route: api.example.com/v1
- route: myapp-internal.apps.internal # internal route (no external access)
no-route: false
random-route: false
# Environment
env:
NODE_ENV: production
LOG_LEVEL: info
FEATURE_FLAG_NEW_UI: "true"
# Services (bound automatically)
services:
- my-postgres
- my-redis
- my-s3-storage
# Stack
stack: cflinuxfs4 # or cflinuxfs3, windows
# Docker (alternative to buildpack)
# docker:
# image: myregistry/myapp:v1
# username: myuser
# Metadata
metadata:
labels:
team: backend
environment: production
annotations:
contact: [email protected]
# Multiple apps in one manifest
- name: myapp-worker
memory: 512M
instances: 2
command: node worker.js
no-route: true # worker, no HTTP
services:
- my-postgres
- my-redis
env:
WORKER_MODE: "true"Multi-environment manifests
# manifest-dev.yml — development overrides
---
applications:
- name: myapp-dev
instances: 1
memory: 128M
env:
NODE_ENV: development
LOG_LEVEL: debug
# Usage:
cf push -f manifest-dev.yml
cf push -f manifest-prod.yml4. Services & Marketplace
# Browse marketplace
cf marketplace # list all available services
cf marketplace -e postgres # details for a service
# Create service instance
cf create-service postgresql small my-postgres
cf create-service redis standard my-redis
cf create-service s3 basic my-storage
# Bind service to app
cf bind-service myapp my-postgres
cf restage myapp # REQUIRED to inject VCAP_SERVICES
# Service keys (for external access)
cf create-service-key my-postgres external-key
cf service-key my-postgres external-key # show credentials
cf delete-service-key my-postgres external-key
# User-provided services (bring your own)
cf create-user-provided-service my-external-db \
-p '{"uri":"postgres://user:pass@external-host:5432/db"}'
cf create-user-provided-service my-syslog \
-l syslog://logs.example.com:514 # log drain
# Update service
cf update-service my-postgres -p medium # upgrade plan
# Delete service
cf unbind-service myapp my-postgres
cf delete-service my-postgresVCAP_SERVICES — How service credentials are injected
{
"postgresql": [{
"name": "my-postgres",
"label": "postgresql",
"plan": "small",
"credentials": {
"host": "10.0.1.50",
"port": 5432,
"database": "cf_abc123",
"username": "cf_user",
"password": "auto-generated-pw",
"uri": "postgres://cf_user:[email protected]:5432/cf_abc123"
}
}]
}Reading VCAP_SERVICES in Node.js
// Direct parsing
const vcap = JSON.parse(process.env.VCAP_SERVICES || '{}');
const pgCreds = vcap.postgresql?.[0]?.credentials;
const dbUrl = pgCreds?.uri || process.env.DATABASE_URL;
// Or use cfenv package
const cfenv = require('cfenv');
const appEnv = cfenv.getAppEnv();
const pgService = appEnv.getService('my-postgres');
const dbUrl = pgService.credentials.uri;5. Buildpacks
# List available buildpacks
cf buildpacks
# Common buildpacks:
# java_buildpack — Java (Spring Boot, etc.)
# nodejs_buildpack — Node.js
# go_buildpack — Go
# python_buildpack — Python
# ruby_buildpack — Ruby
# php_buildpack — PHP
# dotnet_core_buildpack — .NET Core
# staticfile_buildpack — Static HTML/JS/CSS (Nginx)
# binary_buildpack — Pre-compiled binaries
# nginx_buildpack — Custom Nginx config
# Auto-detection order: CF tries each buildpack until one succeeds
# Override: cf push -b nodejs_buildpack
# Multi-buildpack (e.g., Node.js + Nginx for SPA)
cf push myapp -b nodejs_buildpack -b nginx_buildpack
# Custom buildpack (from URL)
cf push myapp -b https://github.com/custom/buildpack.gitStaticfile buildpack (for SPAs)
# Staticfile — tells CF to use staticfile_buildpack
# Create file named "Staticfile" in root:
# Staticfile config
root: build # serve from build/ directory
pushstate: enabled # SPA routing (all 404s → index.html)
force_https: true
http_strict_transport_security: true
host_dot_files: falseProcfile (custom start commands)
# Procfile
web: node server.js
worker: node worker.js6. Multi-Tenancy — Orgs, Spaces & Roles
# Organization management
cf create-org customer-a
cf create-org customer-b
cf orgs
# Space management (within an org)
cf create-space development -o customer-a
cf create-space staging -o customer-a
cf create-space production -o customer-a
cf spaces
# Typical structure:
# Org: customer-a
# ├── Space: development
# │ ├── app: myapp-dev
# │ └── service: postgres-dev
# ├── Space: staging
# │ ├── app: myapp-staging
# │ └── service: postgres-staging
# └── Space: production
# ├── app: myapp-prod (3 instances)
# └── service: postgres-prod
# Quota management
cf create-quota small -m 2G -i 10 -r 20 -s 5
# -m 2G total memory, -i 10 max instances, -r 20 routes, -s 5 services
cf set-org-quota customer-a small
cf create-space-quota team-quota -m 1G -i 5 -r 10 -s 3
cf set-space-quota development team-quota
# Role management
cf set-org-role [email protected] customer-a OrgManager
cf set-org-role [email protected] customer-a OrgAuditor
cf set-space-role [email protected] customer-a development SpaceDeveloper
cf set-space-role [email protected] customer-a production SpaceAuditor
# Roles:
# Org: OrgManager, OrgAuditor, BillingManager
# Space: SpaceManager, SpaceDeveloper, SpaceAuditor, SpaceSupporterRole Permissions Matrix
| Permission | SpaceDeveloper | SpaceManager | SpaceAuditor | OrgManager |
|---|---|---|---|---|
| Push apps | ✅ | ✅ | ❌ | ✅ |
| Start/stop apps | ✅ | ✅ | ❌ | ✅ |
| Create services | ✅ | ✅ | ❌ | ✅ |
| View logs | ✅ | ✅ | ✅ | ✅ |
| Manage routes | ✅ | ✅ | ❌ | ✅ |
| Manage roles | ❌ | ✅ | ❌ | ✅ |
| Create spaces | ❌ | ❌ | ❌ | ✅ |
| View billing | ❌ | ❌ | ❌ | ✅ |
7. Routing & Networking
# Route management
cf routes # list all routes
cf create-route my-space example.com --hostname api
cf map-route myapp example.com --hostname api # api.example.com → myapp
cf map-route myapp example.com --hostname api --path /v2 # path-based routing
cf unmap-route myapp example.com --hostname api
# Blue-green deployment with routes
cf push myapp-v2 -f manifest.yml --no-start
cf start myapp-v2
# Test myapp-v2 on its temp route
cf map-route myapp-v2 example.com --hostname myapp # add prod route to v2
cf unmap-route myapp-v1 example.com --hostname myapp # remove prod route from v1
cf delete myapp-v1 -r # cleanup old version
# Internal routes (container-to-container networking)
cf map-route backend apps.internal --hostname backend-api
# No external access, only other CF apps can reach it
cf add-network-policy frontend backend --protocol tcp --port 8080
# TCP routing (non-HTTP)
cf create-route my-space tcp.example.com --port 1883 # MQTT
cf map-route mqtt-broker tcp.example.com --port 1883
# Route services (middleware)
cf create-user-provided-service rate-limiter \
-r https://rate-limiter.example.com
cf bind-route-service example.com rate-limiter --hostname api
# Wildcard routes
cf map-route myapp example.com --hostname '*' # *.example.comGorouter Architecture
Internet → Load Balancer → Gorouter(s)
│
┌──────────┼──────────┐
▼ ▼ ▼
App Instance App Instance App Instance
(Diego Cell) (Diego Cell) (Diego Cell)
Features:
- Round-robin load balancing
- Sticky sessions (JSESSIONID cookie)
- WebSocket support
- Route integrity checking
- TLS termination
- X-Forwarded headers8. Logging, Monitoring & Troubleshooting
# Logging
cf logs myapp # tail live logs
cf logs myapp --recent # dump recent buffer
# Log types (prefixes):
# APP/PROC/WEB — application stdout/stderr
# RTR — Gorouter access logs
# API — Cloud Controller events
# STG — Staging (buildpack compilation)
# CELL — Diego Cell (container lifecycle)
# SSH — SSH access logs
# Example log output:
# 2026-03-14T08:00:00.00+0100 [APP/PROC/WEB/0] OUT Server started on port 8080
# 2026-03-14T08:00:01.00+0100 [RTR/0] OUT myapp.example.com - [2026-03-14T07:00:01.000Z]
# "GET /api/users HTTP/1.1" 200 0 1234 "-" "curl/7.68.0" ... response_time:0.015
# 2026-03-14T08:00:05.00+0100 [CELL/0] ERR Container became unhealthy
# Log drains (send to external)
cf create-user-provided-service my-drain -l syslog-tls://logs.example.com:6514
cf bind-service myapp my-drain
# Monitoring
cf app myapp # show CPU, memory, disk per instance
cf events myapp # crash events, scaling events
# Troubleshooting
cf ssh myapp # SSH into container
cf ssh myapp -i 2 # SSH into instance 2
cf ssh myapp -c "ls -la /app" # run command
# Common issues:
# App crashes on start → cf logs myapp --recent (check STG + APP logs)
# Out of memory → cf events myapp (look for "out of memory" exit)
# Route not working → cf routes, check cf map-route
# Service binding fails → cf services, check plan/quota
# Staging fails → cf logs myapp --recent (check STG logs for buildpack errors)9. CF on Kubernetes — Korifi & KubeCF
# Korifi — next-gen Cloud Foundry on Kubernetes
# Install via Helm:
helm install korifi cloudfoundry/korifi \
--namespace korifi \
--set api.apiServer=api.cf.example.com \
--set rootNamespace=cf
# Same cf CLI commands work!
cf push myapp # deploys to K8s pods
cf scale myapp -i 3 # scales K8s deployment
cf create-service postgres small my-db # via K8s service broker
# Under the hood:
# cf push → Korifi API → BuildpackBuild CR → kpack → OCI Image → K8s Deployment
# Routes → Contour/Istio Ingress
# Services → Open Service Broker API on K8s
# Benefits of CF on K8s:
# - Developer UX of CF (cf push simplicity)
# - Infrastructure flexibility of K8s
# - No BOSH dependency
# - Leverage K8s ecosystem (monitoring, GitOps, etc.)
# - Gradual migration path from CF to K8s-native
# KubeCF (older approach, uses BOSH inside K8s)
# Less recommended for new deployments — Korifi is the futureCF vs Korifi vs Pure K8s Decision
| Aspect | Traditional CF (BOSH) | Korifi (CF on K8s) | Pure Kubernetes |
|---|---|---|---|
| Ops complexity | High (BOSH) | Medium (K8s) | Medium (K8s) |
| Developer UX | Excellent | Excellent | Manual |
| Maturity | Very mature | Evolving | Very mature |
| Ecosystem | CF marketplace | K8s + CF | K8s native |
| Best for | Existing CF users | CF UX on K8s | Max flexibility |
10. Best Practices & Production Tips
- ▸12-Factor App compliance: CF is built for 12-factor apps — stateless processes, config in env, logs to stdout, backing services as attached resources
- ▸Health checks: Always use http health check type with a /health endpoint
- ▸Memory tuning: Start small (256M), monitor with cf app, increase as needed. Java apps need more (~768M-1G)
- ▸Zero-downtime deploys: Use --strategy rolling or blue-green with route swapping
- ▸Service binding: Always read from VCAP_SERVICES, never hardcode credentials
- ▸Autoscaler: Use CF App Autoscaler for dynamic scaling based on CPU/memory/HTTP throughput/latency
- ▸Security groups: Configure Application Security Groups (ASGs) to control egress traffic
- ▸Stack updates: Keep cflinuxfs4 up to date for security patches (cf stacks)
- ▸Avoid cf restage in production: Use cf restart when possible (restage rebuilds droplet)
- ▸Container-to-container networking: Use internal routes + network policies instead of going through Gorouter
CF App Autoscaler Configuration
{
"instance_min_count": 2,
"instance_max_count": 10,
"scaling_rules": [
{
"metric_type": "cpu",
"threshold": 70,
"operator": ">=",
"adjustment": "+1",
"cool_down_secs": 120
},
{
"metric_type": "cpu",
"threshold": 20,
"operator": "<",
"adjustment": "-1",
"cool_down_secs": 300
},
{
"metric_type": "throughput",
"threshold": 500,
"operator": ">=",
"adjustment": "+2",
"cool_down_secs": 60
}
],
"schedules": {
"timezone": "Europe/Zurich",
"recurring_schedule": [
{
"start_time": "08:00",
"end_time": "18:00",
"days_of_week": [1, 2, 3, 4, 5],
"instance_min_count": 3,
"instance_max_count": 10
}
]
}
}