Docker Compose Parser
Overview
The Docker Compose Parser is a comprehensive PHP designed for programmatic manipulation of Docker Compose YAML files. It provides full bidirectional conversion between YAML and PHP objects with complete type safety and validation.
Architecture & Design Patterns
Interface-Driven Design
All components implement the DockerComposeComponentInterface
:
php
interface DockerComposeComponentInterface
{
public function toArray(): array;
public static function fromArray(string $name, array $data): self;
}
This ensures:
- Consistency: All components have the same serialization interface
- Extensibility: Easy to add new component types
- Testability: Mockable interfaces for unit testing
- Type Safety: Compile-time guarantees on component behavior
Core Components Architecture
DockerCompose (Orchestrator)
├── Service[] (Container definitions)
│ └── BuildConfig (Build specifications)
├── Volume[] (Storage definitions)
├── Network[] (Network topologies)
├── Config[] (Configuration files)
└── Secret[] (Sensitive data)
Data Flow Architecture
YAML File → Symfony\YAML → Array → Component Objects → Validation → Business Logic
↑ ↓
Serialization ← Array ← Component Objects ← Modification ← Processing
Core Components Deep Dive
1. DockerCompose Class
The main orchestrator that handles the complete Docker Compose specification.
Properties & Methods
php
class DockerCompose
{
public readonly ?string $version;
public readonly Collection $services;
public readonly Collection $volumes;
public readonly Collection $networks;
public readonly Collection $configs;
public readonly Collection $secrets;
public readonly array $extra; // Custom x-* properties
// Core methods
public function parse(string $filePath): self
public function toArray(): array
public function toYaml(): string
public function getService(string $name): ?Service
public function addService(string $name, Service $service): self
public function removeService(string $name): self
public function validate(): array // Returns validation errors
}
Parsing Example
php
use Filaship\DockerCompose\DockerCompose;
$parser = new DockerCompose();
$compose = $parser->parse('/path/to/docker-compose.yml');
// Access version
echo "Compose version: " . $compose->version; // "3.8"
// Iterate services
foreach ($compose->services as $name => $service) {
echo "Service: {$name}, Image: {$service->image}\n";
}
// Get specific service
$webService = $compose->getService('web');
if ($webService) {
echo "Web service ports: " . implode(', ', $webService->ports);
}
2. Service Class
Represents individual container services with complete Docker Compose service specification.
Properties
php
class Service implements DockerComposeComponentInterface
{
public readonly string $name;
public readonly ?string $image;
public readonly ?BuildConfig $build;
public readonly ?string $container_name;
public readonly array $ports; // ["80:8080", "443:8443"]
public readonly array $volumes; // ["./data:/data", "cache:/tmp/cache"]
public readonly array $environment; // ["ENV=prod", "DEBUG=false"]
public readonly array $depends_on; // ["db", "redis"]
public readonly array $networks; // ["frontend", "backend"]
public readonly array $labels; // ["traefik.enable=true"]
public readonly mixed $command; // String or array
public readonly ?string $working_dir;
public readonly ?string $user;
public readonly ?string $restart; // "no", "always", "on-failure", "unless-stopped"
public readonly array $extra; // Custom properties
}
Advanced Service Configuration
php
use Filaship\DockerCompose\Service;
use Filaship\DockerCompose\Service\BuildConfig;
// Creating a complex service
$apiService = new Service(
name: 'api',
build: new BuildConfig(
context: './backend',
dockerfile: 'Dockerfile.prod',
args: [
'NODE_ENV' => 'production',
'API_VERSION' => '2.1.0'
],
target: 'runtime',
cache_from: ['node:18-alpine']
),
environment: [
'DATABASE_URL' => '${DATABASE_URL}',
'REDIS_URL' => 'redis://cache:6379',
'JWT_SECRET' => '${JWT_SECRET}',
'LOG_LEVEL' => 'info'
],
ports: [
'3000:3000',
'3001:3001' // Admin interface
],
volumes: [
'./uploads:/app/uploads',
'logs:/app/logs',
'/etc/ssl/certs:/etc/ssl/certs:ro'
],
depends_on: ['database', 'cache', 'migrations'],
networks: ['backend', 'monitoring'],
labels: [
'traefik.enable=true',
'traefik.http.routers.api.rule=Host(`api.company.com`)',
'traefik.http.routers.api.tls=true',
'traefik.http.services.api.loadbalancer.server.port=3000'
],
restart: 'unless-stopped',
working_dir: '/app',
user: 'node:node'
);
3. BuildConfig Class
Handles complex Docker build configurations with full Dockerfile and build argument support.
Properties & Usage
php
class BuildConfig
{
public readonly string $context;
public readonly ?string $dockerfile;
public readonly array $args;
public readonly array $labels;
public readonly ?string $target;
public readonly array $cache_from;
public readonly array $cache_to;
public readonly ?string $network;
public readonly array $extra;
// Static factory method
public static function parse(mixed $buildData): ?self
// Serialization
public function toArray(): array
public function serialize(): array // Optimized for YAML output
}
4. Volume Class
Manages persistent storage configurations with driver options and external volume support.
php
class Volume implements DockerComposeComponentInterface
{
public readonly string $name;
public readonly ?string $driver;
public readonly array $driver_opts;
public readonly mixed $external; // boolean or string
public readonly array $labels;
public readonly array $extra;
}
// Example: Database volume with a specific driver
$dbVolume = new Volume(
name: 'postgres_data',
driver: 'local',
driver_opts: [
'type' => 'bind',
'o' => 'bind',
'device' => '/opt/postgres-data'
],
labels: [
'backup.policy' => 'daily',
'encryption' => 'enabled'
]
);
// External volume reference
$sharedVolume = Volume::fromArray('shared_storage', [
'external' => true,
'labels' => ['tier' => 'shared']
]);
// NFS volume
$nfsVolume = Volume::fromArray('nfs_data', [
'driver' => 'local',
'driver_opts' => [
'type' => 'nfs',
'o' => 'addr=nfs.company.com,rw',
'device' => ':/shared/data'
]
]);
5. Network Class
Defines network topologies with IPAM configuration and custom drivers.
php
class Network implements DockerComposeComponentInterface
{
public readonly string $name;
public readonly ?string $driver;
public readonly array $driver_opts;
public readonly mixed $external;
public readonly array $ipam;
public readonly array $labels;
public readonly array $extra;
}
// Custom bridge network
$frontendNet = new Network(
name: 'frontend',
driver: 'bridge',
driver_opts: [
'com.docker.network.bridge.name' => 'frontend-br',
'com.docker.network.driver.mtu' => '1450'
],
ipam: [
'driver' => 'default',
'config' => [
[
'subnet' => '172.20.0.0/16',
'gateway' => '172.20.0.1',
'ip_range' => '172.20.240.0/20'
]
]
],
labels: [
'environment' => 'production',
'security.zone' => 'frontend'
]
);
// Overlay network for Swarm
$overlayNet = Network::fromArray('backend', [
'driver' => 'overlay',
'driver_opts' => [
'encrypted' => 'true'
],
'attachable' => true,
'ipam' => [
'config' => [['subnet' => '10.0.0.0/24']]
]
]);
Practical Usage Examples
Example 1: Parsing and Analyzing Existing Compose Files
php
use Filaship\DockerCompose\DockerCompose;
function analyzeComposeFile(string $filePath): array
{
$parser = new DockerCompose();
$compose = $parser->parse($filePath);
$analysis = [
'metadata' => [
'version' => $compose->version,
'file_path' => $filePath,
'parsed_at' => now()->toISOString()
],
'services' => [
'total' => $compose->services->count(),
'with_build' => $compose->services->filter(fn($s) => $s->build !== null)->count(),
'with_image' => $compose->services->filter(fn($s) => $s->image !== null)->count(),
'with_ports' => $compose->services->filter(fn($s) => !empty($s->ports))->count(),
],
'infrastructure' => [
'volumes' => $compose->volumes->count(),
'networks' => $compose->networks->count(),
'configs' => $compose->configs->count(),
'secrets' => $compose->secrets->count(),
],
'security_analysis' => analyzeSecurityIssues($compose),
'port_mappings' => extractPortMappings($compose),
'dependencies' => buildDependencyGraph($compose)
];
return $analysis;
}
function analyzeSecurityIssues(DockerCompose $compose): array
{
$issues = [];
foreach ($compose->services as $name => $service) {
// Check for privileged containers
if (isset($service->extra['privileged']) && $service->extra['privileged']) {
$issues[] = "Service '{$name}' runs in privileged mode";
}
// Check for exposed sensitive ports
foreach ($service->ports as $port) {
if (str_contains($port, ':22') || str_contains($port, ':3306')) {
$issues[] = "Service '{$name}' exposes potentially sensitive port: {$port}";
}
}
// Check for root user
if ($service->user === 'root' || $service->user === '0') {
$issues[] = "Service '{$name}' runs as root user";
}
}
return $issues;
}
function extractPortMappings(DockerCompose $compose): array
{
$mappings = [];
foreach ($compose->services as $name => $service) {
foreach ($service->ports as $port) {
if (preg_match('/^(\d+):(\d+)$/', $port, $matches)) {
$mappings[$name][] = [
'host_port' => (int) $matches[1],
'container_port' => (int) $matches[2],
'protocol' => 'tcp'
];
}
}
}
return $mappings;
}
function buildDependencyGraph(DockerCompose $compose): array
{
$graph = [];
foreach ($compose->services as $name => $service) {
$graph[$name] = [
'depends_on' => $service->depends_on,
'networks' => $service->networks
];
}
return $graph;
}
Example 2: Programmatic Compose File Generation
php
use Filaship\DockerCompose\{DockerCompose, Service, Volume, Network};
use Filaship\DockerCompose\Service\BuildConfig;
use Illuminate\Support\Collection;
function createMicroservicesStack(): DockerCompose
{
// Create shared infrastructure
$networks = new Collection([
'frontend' => new Network(
name: 'frontend',
driver: 'bridge'
),
'backend' => new Network(
name: 'backend',
driver: 'bridge',
driver_opts: ['com.docker.network.driver.mtu' => '1450']
),
'monitoring' => new Network(
name: 'monitoring',
driver: 'bridge'
)
]);
$volumes = new Collection([
'postgres_data' => new Volume(
name: 'postgres_data',
driver: 'local'
),
'redis_data' => new Volume(
name: 'redis_data',
driver: 'local'
),
'prometheus_data' => new Volume(
name: 'prometheus_data',
driver: 'local'
)
]);
// Create services
$services = new Collection();
// Database service
$services->put('database', new Service(
name: 'database',
image: 'postgres:15-alpine',
environment: [
'POSTGRES_DB=microservices',
'POSTGRES_USER=admin',
'POSTGRES_PASSWORD=${DB_PASSWORD}'
],
volumes: ['postgres_data:/var/lib/postgresql/data'],
networks: ['backend'],
ports: ['5432:5432'],
restart: 'unless-stopped'
));
// Cache service
$services->put('cache', new Service(
name: 'cache',
image: 'redis:7-alpine',
volumes: ['redis_data:/data'],
networks: ['backend'],
command: 'redis-server --appendonly yes',
restart: 'unless-stopped'
));
// API Gateway
$services->put('gateway', new Service(
name: 'gateway',
build: new BuildConfig(
context: './gateway',
dockerfile: 'Dockerfile',
args: ['NODE_ENV' => 'production']
),
environment: [
'SERVICE_DISCOVERY_URL=http://consul:8500',
'REDIS_URL=redis://cache:6379'
],
ports: ['80:3000', '443:3443'],
depends_on: ['cache'],
networks: ['frontend', 'backend'],
restart: 'unless-stopped'
));
// User service
$services->put('user-service', createMicroservice(
name: 'user-service',
context: './services/user',
port: '3001',
dependencies: ['database', 'cache']
));
// Order service
$services->put('order-service', createMicroservice(
name: 'order-service',
context: './services/order',
port: '3002',
dependencies: ['database', 'cache', 'user-service']
));
// Monitoring stack
$services->put('prometheus', new Service(
name: 'prometheus',
image: 'prom/prometheus:latest',
volumes: [
'./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro',
'prometheus_data:/prometheus'
],
ports: ['9090:9090'],
networks: ['monitoring', 'backend'],
command: [
'--config.file=/etc/prometheus/prometheus.yml',
'--storage.tsdb.path=/prometheus',
'--web.console.libraries=/etc/prometheus/console_libraries',
'--web.console.templates=/etc/prometheus/consoles',
'--storage.tsdb.retention.time=200h',
'--web.enable-lifecycle'
]
));
return new DockerCompose(
version: '3.8',
services: $services,
volumes: $volumes,
networks: $networks
);
}
function createMicroservice(string $name, string $context, string $port, array $dependencies = []): Service
{
return new Service(
name: $name,
build: new BuildConfig(
context: $context,
dockerfile: 'Dockerfile',
args: [
'NODE_ENV' => 'production',
'SERVICE_NAME' => $name
]
),
environment: [
'SERVICE_NAME=' . $name,
'DATABASE_URL=${DATABASE_URL}',
'REDIS_URL=redis://cache:6379',
'LOG_LEVEL=info'
],
ports: ["{$port}:3000"],
depends_on: $dependencies,
networks: ['backend'],
restart: 'unless-stopped',
labels: [
'traefik.enable=true',
"traefik.http.routers.{$name}.rule=PathPrefix(`/{$name}`)",
"traefik.http.services.{$name}.loadbalancer.server.port=3000"
]
);
}
// Usage
$compose = createMicroservicesStack();
file_put_contents('./docker-compose.production.yml', $compose->toYaml());