← Back to Observability

Cloud Foundry Cheatsheet

cf push, Services, Buildpacks, Multi-Tenancy, Routing & Operations

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

AspectCloud FoundryKubernetes
Abstraction levelApplication (PaaS)Container (CaaS)
Developer experiencecf push (seconds)Dockerfile + YAML + kubectl (minutes-hours)
Learning curveLow — focus on app, not infraHigh — must understand pods, services, ingress, etc.
BuildpacksBuilt-in (auto-detect language)Need to add (Paketo/Cloud Native Buildpacks)
Service bindingNative marketplace (cf create-service)Operators/Helm charts, manual config
Multi-tenancyBuilt-in (Orgs → Spaces → Roles)Namespaces + RBAC (manual setup)
RoutingBuilt-in (Gorouter), automaticIngress controller needed (NGINX/Traefik)
Scalingcf scale app -i 5HPA, VPA, manual replicas
Health managementAuto-restarts, crash recoveryLiveness/readiness probes (you configure)
Loggingcf logs (Loggregator) built-inNeed Loki/EFK stack
MetricsBuilt-in (Firehose/Nozzle)Prometheus + custom setup
Service meshBuilt-in routing, no extraIstio/Linkerd (complex)
Best forApp developers, rapid deploymentInfra teams, microservices at scale
OverheadHigher (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 / LDAP

2. 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 target

Push & 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 disk

App 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-v2

Scaling

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 once

Logs & 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 access

Environment 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 domain

3. 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.yml

4. 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-postgres

VCAP_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.git

Staticfile 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: false

Procfile (custom start commands)

# Procfile
web: node server.js
worker: node worker.js

6. 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, SpaceSupporter

Role Permissions Matrix

PermissionSpaceDeveloperSpaceManagerSpaceAuditorOrgManager
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.com

Gorouter 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 headers

8. 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 future

CF vs Korifi vs Pure K8s Decision

AspectTraditional CF (BOSH)Korifi (CF on K8s)Pure Kubernetes
Ops complexityHigh (BOSH)Medium (K8s)Medium (K8s)
Developer UXExcellentExcellentManual
MaturityVery matureEvolvingVery mature
EcosystemCF marketplaceK8s + CFK8s native
Best forExisting CF usersCF UX on K8sMax 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
      }
    ]
  }
}