Skip to main content
Version: Next 🚧

Deployment Architecture

This document describes the orchestration-agnostic deployment architecture for UPF applications, supporting Docker Compose, Kubernetes, Docker Swarm, and other container orchestrators.

Overview​

UPF is designed to be orchestration-agnostic, meaning the same plugin containers can be deployed across different orchestration platforms without modification. This is achieved through:

  • Standard container configuration (container.yaml) that gets translated to orchestrator-specific formats
  • DNS-based service discovery that works across all platforms
  • Environment-based configuration for runtime settings
  • Health check standardization using gRPC health protocol

Container Configuration​

Standard Container Config​

Each plugin defines a container.yaml that is orchestrator-agnostic:

# plugins/my-plugin/container.yaml
plugin:
id: my-business-plugin
version: 1.0.0

container:
image: upf/my-business-plugin:1.0.0

# Network ports
ports:
- name: grpc
port: 50051
protocol: TCP
- name: http
port: 8080
protocol: TCP
- name: metrics
port: 9090
protocol: TCP

# Environment variables
environment:
- name: PLUGIN_ID
value: my-business-plugin
- name: GRPC_PORT
value: '50051'
- name: HTTP_PORT
value: '8080'
- name: LOG_LEVEL
value: info
- name: REGISTRY_URL
valueFrom:
configMap: upf-config
key: registry.url

# Health checks
healthCheck:
grpc:
port: 50051
service: grpc.health.v1.Health
http:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

# Resource requirements
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi

# Scaling configuration
scaling:
replicas:
min: 1
max: 10
metrics:
- type: cpu
target: 70
- type: memory
target: 80

# Volume mounts
volumes:
- name: config
mountPath: /app/config
configMap: my-plugin-config
- name: secrets
mountPath: /app/secrets
secret: my-plugin-secrets

# Dependencies (for startup ordering)
dependsOn:
- registry
- storage-plugin
- cache-plugin

Docker Compose Deployment​

Generated docker-compose.yml​

# deployments/docker-compose/docker-compose.yml
version: '3.8'

services:
# Core Services
registry:
image: upf/registry:1.0.0
ports:
- '50052:50051'
environment:
- GRPC_PORT=50051
- LOG_LEVEL=info
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

gateway:
image: upf/gateway:1.0.0
ports:
- '8080:8080'
- '50051:50051'
environment:
- GRPC_PORT=50051
- HTTP_PORT=8080
- REGISTRY_URL=registry:50051
depends_on:
registry:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

# Infrastructure Plugins
auth-keycloak:
image: upf/auth-keycloak:1.0.0
environment:
- GRPC_PORT=50051
- REGISTRY_URL=registry:50051
- STORAGE_URL=storage-postgres:50051
depends_on:
registry:
condition: service_healthy
storage-postgres:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

storage-postgres:
image: upf/storage-postgres:1.0.0
environment:
- GRPC_PORT=50051
- DATABASE_URL=postgres://postgres:password@postgres:5432/upf
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

cache-redis:
image: upf/cache-redis:1.0.0
environment:
- GRPC_PORT=50051
- REDIS_URL=redis://redis:6379
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

message-bus-nats:
image: upf/message-bus-nats:1.0.0
environment:
- GRPC_PORT=50051
- NATS_URL=nats://nats:4222
depends_on:
nats:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

# Business Plugins
my-business-plugin:
image: upf/my-business-plugin:1.0.0
environment:
- GRPC_PORT=50051
- REGISTRY_URL=registry:50051
depends_on:
registry:
condition: service_healthy
storage-postgres:
condition: service_healthy
cache-redis:
condition: service_healthy
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 10s
timeout: 5s
retries: 3
networks:
- upf-network

# External Dependencies
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=upf
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 5s
timeout: 5s
retries: 5
networks:
- upf-network

redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 5
networks:
- upf-network

nats:
image: nats:2.10-alpine
command: ['--jetstream', '--store_dir=/data']
volumes:
- nats-data:/data
healthcheck:
test: ['CMD', 'nats-server', '--help']
interval: 5s
timeout: 5s
retries: 5
networks:
- upf-network

networks:
upf-network:
driver: bridge

volumes:
postgres-data:
redis-data:
nats-data:

Docker Compose Commands​

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f

# Scale a service
docker-compose up -d --scale my-business-plugin=3

# Stop all services
docker-compose down

# Stop and remove volumes
docker-compose down -v

Kubernetes Deployment​

Kubernetes Manifests Structure​

deployments/kubernetes/
├── base/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ ├── configmap.yaml
│ ├── secrets.yaml
│ ├── registry/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ ├── gateway/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── ingress.yaml
│ └── plugins/
│ ├── auth-keycloak/
│ ├── storage-postgres/
│ ├── cache-redis/
│ └── message-bus-nats/
└── overlays/
├── development/
│ └── kustomization.yaml
├── staging/
│ └── kustomization.yaml
└── production/
├── kustomization.yaml
└── hpa.yaml

Example Kubernetes Deployment​

# deployments/kubernetes/base/plugins/my-business-plugin/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-business-plugin
namespace: upf
labels:
app: my-business-plugin
tier: business
spec:
replicas: 2
selector:
matchLabels:
app: my-business-plugin
template:
metadata:
labels:
app: my-business-plugin
tier: business
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '9090'
spec:
containers:
- name: my-business-plugin
image: upf/my-business-plugin:1.0.0
ports:
- name: grpc
containerPort: 50051
protocol: TCP
- name: http
containerPort: 8080
protocol: TCP
- name: metrics
containerPort: 9090
protocol: TCP
env:
- name: GRPC_PORT
value: '50051'
- name: HTTP_PORT
value: '8080'
- name: REGISTRY_URL
valueFrom:
configMapKeyRef:
name: upf-config
key: registry.url
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
grpc:
port: 50051
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
grpc:
port: 50051
initialDelaySeconds: 15
periodSeconds: 20
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
volumes:
- name: config
configMap:
name: my-business-plugin-config

---
apiVersion: v1
kind: Service
metadata:
name: my-business-plugin
namespace: upf
spec:
selector:
app: my-business-plugin
ports:
- name: grpc
port: 50051
targetPort: 50051
- name: http
port: 8080
targetPort: 8080
- name: metrics
port: 9090
targetPort: 9090

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-business-plugin
namespace: upf
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-business-plugin
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

Kubernetes Gateway Ingress​

# deployments/kubernetes/base/gateway/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: upf-gateway
namespace: upf
annotations:
nginx.ingress.kubernetes.io/backend-protocol: 'GRPC'
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: upf-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway
port:
number: 50051

Service Discovery​

DNS-Based Discovery​

All orchestrators provide DNS-based service discovery:

Service Discovery Configuration​

// Plugin service discovery using environment-based URLs
interface ServiceDiscoveryConfig {
registryUrl: string; // REGISTRY_URL env var
useEnvDiscovery: boolean; // Use env vars for direct URLs
}

// Resolution priority:
// 1. Environment variable (e.g., STORAGE_URL)
// 2. Registry lookup (resolve interface to endpoint)
// 3. DNS-based discovery (service-name:port)

async function resolveService(interfaceName: string, config: ServiceDiscoveryConfig): Promise<string> {
// Check for direct environment variable
const envKey = `${interfaceName.toUpperCase()}_URL`;
const envUrl = process.env[envKey];
if (envUrl) return envUrl;

// Query registry for provider
const registry = createRegistryClient(config.registryUrl);
const endpoint = await registry.resolveEndpoint({ interface: interfaceName });

return `${endpoint.host}:${endpoint.port}`;
}

Configuration Management​

Environment Variables​

Configuration Schema​

// Plugin configuration loading
interface PluginConfig {
// Required
pluginId: string;
grpcPort: number;
registryUrl: string;

// Optional with defaults
httpPort?: number;
metricsPort?: number;
logLevel?: 'debug' | 'info' | 'warn' | 'error';

// Interface dependencies (resolved at runtime)
interfaces?: {
[key: string]: string; // interface name -> URL
};
}

function loadConfig(): PluginConfig {
return {
pluginId: requireEnv('PLUGIN_ID'),
grpcPort: parseInt(requireEnv('GRPC_PORT'), 10),
registryUrl: requireEnv('REGISTRY_URL'),
httpPort: optionalInt('HTTP_PORT', 8080),
metricsPort: optionalInt('METRICS_PORT', 9090),
logLevel: optionalEnum('LOG_LEVEL', ['debug', 'info', 'warn', 'error'], 'info'),
interfaces: loadInterfaceConfig(),
};
}

function loadInterfaceConfig(): Record<string, string> {
const interfaces: Record<string, string> = {};

// Load from environment variables
for (const [key, value] of Object.entries(process.env)) {
if (key.endsWith('_URL') && value) {
const interfaceName = key.replace('_URL', '');
interfaces[interfaceName] = value;
}
}

return interfaces;
}

Health Checks​

gRPC Health Check Protocol​

All plugins implement the standard gRPC health check protocol:

// grpc/health/v1/health.proto
syntax = "proto3";

package grpc.health.v1;

service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

message HealthCheckRequest {
string service = 1;
}

message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3;
}
ServingStatus status = 1;
}

Health Check Implementation​

// packages/sdk/src/health.ts

import { HealthCheckResponse, ServingStatus } from './generated/health';

interface HealthChecker {
name: string;
check(): Promise<boolean>;
}

class HealthService {
private checkers: HealthChecker[] = [];
private status: ServingStatus = ServingStatus.NOT_SERVING;

addChecker(checker: HealthChecker): void {
this.checkers.push(checker);
}

async check(service?: string): Promise<HealthCheckResponse> {
if (service && service !== '' && service !== this.pluginId) {
return { status: ServingStatus.SERVICE_UNKNOWN };
}

try {
const results = await Promise.all(
this.checkers.map(async (checker) => {
try {
return await checker.check();
} catch {
return false;
}
}),
);

const healthy = results.every((r) => r);
return {
status: healthy ? ServingStatus.SERVING : ServingStatus.NOT_SERVING,
};
} catch {
return { status: ServingStatus.NOT_SERVING };
}
}

setServing(): void {
this.status = ServingStatus.SERVING;
}

setNotServing(): void {
this.status = ServingStatus.NOT_SERVING;
}
}

Deployment Topology​

Single-Node Development​

Multi-Node Production​

Deployment Workflow​

CI/CD Pipeline​

Deployment Commands​

# Development deployment
upf deploy --env development

# Staging deployment
upf deploy --env staging

# Production deployment (with approval)
upf deploy --env production --require-approval

# Rolling update
upf deploy --strategy rolling --max-unavailable 25%

# Blue-green deployment
upf deploy --strategy blue-green

# Canary deployment
upf deploy --strategy canary --weight 10

Monitoring and Observability​

Metrics Collection​

Logging​

# Structured logging configuration
logging:
level: info
format: json
fields:
service: ${PLUGIN_ID}
version: ${PLUGIN_VERSION}
environment: ${ENVIRONMENT}
outputs:
- type: stdout
- type: file
path: /var/log/upf/${PLUGIN_ID}.log

Previous: UI Federation Next: Plugin Manifest Specification