This document explains how the PHP buildpack handles Cloud Foundry service bindings (VCAP_SERVICES) and compares our approach with other Cloud Foundry buildpacks.
- Quick Summary
- VCAP_SERVICES Availability
- How PHP Buildpack v5.x Uses VCAP_SERVICES
- Comparison with Other Buildpacks
- Migration from v4.x
- Best Practices
TL;DR:
- ✅ VCAP_SERVICES IS available during staging (in Go code)
- ✅ Extensions CAN read VCAP_SERVICES to configure agents
- ✅ Can write profile.d scripts with parsed service credentials
- ❌
@{VCAP_SERVICES}NOT available as config file placeholder - ✅ PHP v5.x follows same patterns as all other CF buildpacks
Cloud Foundry provides VCAP_SERVICES as an environment variable during both staging and runtime:
| Phase | VCAP_SERVICES Available? | How to Access |
|---|---|---|
| Staging (Supply/Finalize) | ✅ Yes | os.Getenv("VCAP_SERVICES") in Go code |
| Runtime (Container Startup) | ✅ Yes | getenv('VCAP_SERVICES') in PHP code or $VCAP_SERVICES in shell |
Important: VCAP_SERVICES is available during staging, allowing buildpacks to:
- Detect bound services
- Extract credentials
- Configure agents and extensions
- Write configuration files
During the supply phase, the extension framework automatically parses VCAP_SERVICES:
Code Location: src/php/extensions/extension.go:77-82
// Parse VCAP_SERVICES
if vcapServicesJSON := os.Getenv("VCAP_SERVICES"); vcapServicesJSON != "" {
if err := json.Unmarshal([]byte(vcapServicesJSON), &ctx.VcapServices); err != nil {
return nil, fmt.Errorf("failed to parse VCAP_SERVICES: %w", err)
}
}This makes VCAP_SERVICES available to all extensions via ctx.VcapServices.
Code Location: src/php/extensions/newrelic/newrelic.go
// Writes a profile.d script that extracts license key at runtime
const newrelicEnvScript = `if [[ -z "${NEWRELIC_LICENSE:-}" ]]; then
export NEWRELIC_LICENSE=$(echo $VCAP_SERVICES | jq -r '.newrelic[0].credentials.licenseKey')
fi`What it does:
- During staging: Creates profile.d script
- At runtime: Script extracts NewRelic license from VCAP_SERVICES
Code Location: src/php/extensions/sessions/sessions.go
func (e *SessionsExtension) loadSession(ctx *extensions.Context) BaseSetup {
// Search for appropriately named session store in VCAP_SERVICES
for _, services := range ctx.VcapServices {
for _, service := range services {
serviceName := service.Name
// Check if service matches Redis or Memcached patterns
if strings.Contains(strings.ToLower(serviceName), "redis") {
return &RedisSetup{Service: service}
}
if strings.Contains(strings.ToLower(serviceName), "memcache") {
return &MemcachedSetup{Service: service}
}
}
}
return nil
}What it does:
- During staging: Parses VCAP_SERVICES to find Redis/Memcached services
- Configures PHP session handler accordingly
- Writes php.ini with session configuration
Code Location: src/php/extensions/appdynamics/appdynamics.go
Similar pattern - reads VCAP_SERVICES during staging to configure agent.
After analyzing Go, Java, Ruby, and Python buildpacks, we found all buildpacks use VCAP_SERVICES the same way:
Code Location: go-buildpack/src/go/hooks/appdynamics.go:75
func (h AppdynamicsHook) BeforeCompile(stager *libbuildpack.Stager) error {
vcapServices := os.Getenv("VCAP_SERVICES")
services := make(map[string][]Plan)
err := json.Unmarshal([]byte(vcapServices), &services)
if val, ok := services["appdynamics"]; ok {
// Configure AppDynamics agent
// Write profile.d script with environment variables
}
}Code Location: java-buildpack/src/java/common/context.go:106
func GetVCAPServices() (VCAPServices, error) {
vcapServicesStr := os.Getenv("VCAP_SERVICES")
if vcapServicesStr == "" {
return VCAPServices{}, nil
}
// Parse and return services
}Used in multiple frameworks (Sealights, JVMKill, etc.)
Similar patterns - all read VCAP_SERVICES during staging to configure services.
Config File Placeholders: No buildpack (except PHP v4.x) ever supported using @{VCAP_SERVICES} or other runtime environment variables as config file placeholders.
| Feature | PHP v4.x | PHP v5.x | Go | Java | Ruby | Python |
|---|---|---|---|---|---|---|
| Read VCAP_SERVICES in code (staging) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Configure from VCAP_SERVICES | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Write profile.d scripts | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| @{VCAP_SERVICES} in config files | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
Key Insight: The runtime config rewrite feature (using @{VCAP_SERVICES} in config files) was unique to PHP v4.x and not a standard Cloud Foundry pattern.
PHP v4.x had two mechanisms for using VCAP_SERVICES:
- Staging-time (like v5.x): Extensions read VCAP_SERVICES in Python code
- Runtime (removed in v5.x):
bin/rewritescript allowed@{VCAP_SERVICES}in config files
PHP v5.x removed mechanism #2, aligning with all other Cloud Foundry buildpacks.
v4.x (NO LONGER WORKS):
# .bp-config/php/fpm.d/db.conf
[www]
env[DB_HOST] = @{VCAP_SERVICES} ; ← Runtime rewrite expanded thisv5.x Migration Option 1 - Application Code:
<?php
// Parse in application code
$vcap = json_decode(getenv('VCAP_SERVICES'), true);
$db = $vcap['mysql'][0]['credentials'];
$host = $db['host'];
?>v5.x Migration Option 2 - profile.d Script:
#!/bin/bash
# .profile.d/parse-vcap.sh
export DB_HOST=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.host')
export DB_PORT=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.port')
export DB_NAME=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.name')Then in FPM config:
[www]
env[DB_HOST] = ${DB_HOST}
env[DB_PORT] = ${DB_PORT}
env[DB_NAME] = ${DB_NAME}v4.x (NO LONGER WORKS):
[www]
env[INSTANCE_INDEX] = @{CF_INSTANCE_INDEX}v5.x Migration - Shell Variables:
[www]
env[INSTANCE_INDEX] = ${CF_INSTANCE_INDEX}Or read in application code:
<?php
$instanceIndex = getenv('CF_INSTANCE_INDEX');
?>For common services, let extensions handle VCAP_SERVICES automatically:
NewRelic:
# Just bind the service
cf bind-service my-app my-newrelic-service
# Extension automatically configures NewRelicRedis/Memcached Sessions:
# Bind Redis service
cf bind-service my-app my-redis
# Extension automatically configures PHP sessionsFor custom service parsing:
File: .profile.d/parse-services.sh
#!/bin/bash
# Extract database credentials
if [[ -n "$VCAP_SERVICES" ]]; then
export DB_HOST=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.host')
export DB_PORT=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.port')
export DB_USER=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.username')
export DB_PASS=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.password')
export DB_NAME=$(echo $VCAP_SERVICES | jq -r '.mysql[0].credentials.name')
fiThen use in PHP:
<?php
$host = getenv('DB_HOST');
$port = getenv('DB_PORT');
// ...
?>For complex service logic:
<?php
class VcapParser {
private $services;
public function __construct() {
$vcapJson = getenv('VCAP_SERVICES');
$this->services = $vcapJson ? json_decode($vcapJson, true) : [];
}
public function getService($label, $name = null) {
if (!isset($this->services[$label])) {
return null;
}
$services = $this->services[$label];
if ($name === null) {
return $services[0] ?? null;
}
foreach ($services as $service) {
if ($service['name'] === $name) {
return $service;
}
}
return null;
}
public function getCredentials($label, $name = null) {
$service = $this->getService($label, $name);
return $service ? $service['credentials'] : null;
}
}
// Usage
$vcap = new VcapParser();
$mysqlCreds = $vcap->getCredentials('mysql');
$host = $mysqlCreds['host'];
?># DOES NOT WORK - Not a supported placeholder
[www]
env[SERVICES] = @{VCAP_SERVICES}# If you change service bindings:
cf unbind-service my-app old-db
cf bind-service my-app new-db
# Must restage to pick up new VCAP_SERVICES in config:
cf restage my-app # Required!
cf restart my-app # Not sufficient if using build-time configException: If using ${VCAP_SERVICES} in shell contexts or reading in PHP code, restart is sufficient.
The PHP buildpack v5.x follows the same VCAP_SERVICES patterns as all other Cloud Foundry buildpacks:
- ✅ Read VCAP_SERVICES during staging
- ✅ Configure extensions and agents
- ✅ Write profile.d scripts
- ✅ Parse and extract service credentials
- ❌ No config file placeholders for arbitrary env vars
The ability to use @{VCAP_SERVICES} in config files was:
- Unique to PHP v4.x - No other buildpack had this
- Removed for good reasons:
- Performance (no runtime rewriting)
- Security (reduced attack surface)
- Predictability (configs locked at staging)
- Alignment with other buildpacks
All v4.x VCAP_SERVICES use cases have clear v5.x equivalents:
- Extension-based configuration (same as v4.x)
- Profile.d scripts (standard CF pattern)
- Application code parsing (standard practice)
For detailed migration examples, see REWRITE_MIGRATION.md.
- REWRITE_MIGRATION.md - Complete v4.x to v5.x migration guide
- Cloud Foundry VCAP_SERVICES Documentation
- PHP Extensions Guide - How to create custom extensions