ICP Platform - Docker Integration Guide
Complete guide for integrating ICP (Identity Control Plane) workload identity with Docker containers and Docker Compose.
Table of Contents
- Prerequisites
- Architecture Overview
- Installation
- Workload Registration
- SDK Integration
- Production Deployment
- Troubleshooting
- Best Practices
Prerequisites
System Requirements
- Docker Engine: 20.10+ (with compose plugin)
- Docker Compose: 2.0+ (or standalone 1.29+)
- Operating System: Linux, macOS, Windows with WSL2
- RAM: 2GB minimum (agent + workloads)
- Network: Outbound HTTPS (443) to ICP Server
Installation
# Install Docker Engine
curl -fsSL https://get.docker.com | sh
# Install Docker Compose (if not included)
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker --version
docker compose version
Credentials
You'll need:
- Tenant ID - Provided by AuthSec
- ICP Server URL - Provided by AuthSec
- Node ID - Unique identifier for this Docker host (e.g.,
docker-prod-host-01)
Architecture Overview
Docker Deployment Architecture
┌──────────────────────────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ICP Agent Container │ │
│ │ │ │
│ │ Image: icp-agent:latest │ │
│ │ Network: host / bridge │ │
│ │ Volumes: │ │
│ │ - agent-socket (shared with workloads) │ │
│ │ - /var/run/docker.sock (for Docker API access) │ │
│ │ │ │
│ │ Env: │ │
│ │ ICP_AGENT_AGENT__TENANT_ID=your-tenant-id-here │ │
│ │ ICP_AGENT_AGENT__NODE_ID=docker-prod-host-01 │ │
│ │ ICP_AGENT_ICP_SERVICE__ADDRESS=https://icp-server... │ │
│ └────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ │ Unix Socket │
│ │ (agent-socket volume) │
│ │ │
│ ┌──────────────────────┴────────────────────┐ │
│ │ │ │
│ ┌──────▼─────────────────┐ ┌─────────────────▼──────────┐ │
│ │ Workload Container A │ │ Workload Container B │ │
│ │ (web-service) │ │ (api-service) │ │
│ │ │ │ │ │
│ │ Volumes: │ │ Volumes: │ │
│ │ - agent-socket │ │ - agent-socket │ │
│ │ │ │ │ │
│ │ Env: │ │ Env: │ │
│ │ DOCKER_LABEL_APP= │ │ DOCKER_LABEL_APP= │ │
│ │ web-service │ │ api-service │ │
│ │ DOCKER_CONTAINER_NAME│ │ DOCKER_CONTAINER_NAME │ │
│ │ DOCKER_IMAGE_NAME │ │ DOCKER_IMAGE_NAME │ │
│ │ │ │ │ │
│ │ Labels: │ │ Labels: │ │
│ │ app=web-service │ │ app=api-service │ │
│ │ env=production │ │ env=production │ │
│ └────────────────────────┘ └────────────────────────────┘ │
│ │
└──────────────────────────────────┬───────────────────────────────────┘
│
│ HTTPS + mTLS
▼
┌──────────────────────────────┐
│ ICP Server (SaaS) │
│ - Agent SVID Issuance │
│ - Workload SVID Issuance │
└──────────────────────────────┘
How Docker Attestation Works
The agent uses the Docker Plugin to collect container metadata as selectors:
| Selector | Description | Example |
|---|---|---|
docker:label:<key> | Container label | "docker:label:app": "web-service" |
docker:container-id | Full container ID | "abc123def456..." |
docker:container-name | Container name | "web-service" |
docker:image-name | Image name | "my-app:latest" |
docker:image-id | Image ID | "sha256:abc123..." |
Metadata is collected via:
- Docker API (agent queries
/var/run/docker.sock) - Environment Variables (SDK sends metadata via gRPC headers)
Installation
Quick Start with Docker Compose
This is the recommended method for most deployments.
Step 1: Create docker-compose.yml
mkdir icp-demo
cd icp-demo
cat > docker-compose.yml <<'EOF'
version: '3.8'
services:
# ICP Agent
icp-agent:
image: your-docker-registry.example.com/icp-agent:latest
container_name: icp-agent
hostname: icp-agent-docker
environment:
# Tenant configuration
- ICP_AGENT_AGENT__TENANT_ID=your-tenant-id-here
- ICP_AGENT_AGENT__NODE_ID=docker-prod-host-01
# ICP Server connection
- ICP_AGENT_ICP_SERVICE__ADDRESS=https://dev.api.authsec.dev/spiresvc
# Attestation
- ICP_AGENT_ATTESTATION__TYPE=auto
# Logging
- ICP_AGENT_LOGGING__LEVEL=info
- ICP_AGENT_LOGGING__FORMAT=json
volumes:
# Shared socket for workloads
- agent-socket:/run/spire/sockets
# Docker API access (for container attestation)
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- icp-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Example Workload A (Web Service)
web-service:
build: ./web-service
container_name: web-service
hostname: web-service
depends_on:
icp-agent:
condition: service_healthy
environment:
# SPIFFE socket
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
# Docker metadata (for attestation)
- DOCKER_CONTAINER_ID=${HOSTNAME}
- DOCKER_CONTAINER_NAME=web-service
- DOCKER_IMAGE_NAME=web-service:latest
# Application config
- PORT=8080
- LOG_LEVEL=info
volumes:
# Mount agent socket
- agent-socket:/run/spire/sockets:ro
networks:
- icp-network
ports:
- "8080:8080"
labels:
# CRITICAL: These labels are used for attestation
- "app=web-service"
- "env=production"
- "version=v1"
restart: unless-stopped
# Example Workload B (API Service)
api-service:
build: ./api-service
container_name: api-service
hostname: api-service
depends_on:
icp-agent:
condition: service_healthy
environment:
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
- DOCKER_CONTAINER_ID=${HOSTNAME}
- DOCKER_CONTAINER_NAME=api-service
- DOCKER_IMAGE_NAME=api-service:latest
- PORT=8443
volumes:
- agent-socket:/run/spire/sockets:ro
networks:
- icp-network
ports:
- "8443:8443"
labels:
- "app=api-service"
- "env=production"
- "version=v1"
restart: unless-stopped
volumes:
agent-socket:
driver: local
networks:
icp-network:
driver: bridge
EOF
Step 2: Create Example Workload
Directory Structure:
icp-demo/
├── docker-compose.yml
├── web-service/
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
└── api-service/
├── Dockerfile
├── app.py
└── requirements.txt
File: web-service/requirements.txt
git+https://github.com/authsec-ai/sdk-authsec.git
fastapi
uvicorn
httpx
File: web-service/app.py
import asyncio
from authsec_sdk import QuickStartSVID
from fastapi import FastAPI
import uvicorn
import httpx
app = FastAPI()
svid = None
@app.on_event("startup")
async def startup():
global svid
svid = await QuickStartSVID.initialize()
print(f"✅ Web Service authenticated as: {svid.spiffe_id}")
@app.get("/healthz")
async def health():
return {"status": "healthy"}
@app.get("/")
async def root():
return {
"service": "web-service",
"spiffe_id": svid.spiffe_id if svid else None
}
@app.get("/call-api")
async def call_api():
"""Call API service with mTLS"""
ssl_context = svid.create_ssl_context_for_client()
async with httpx.AsyncClient(verify=ssl_context) as client:
response = await client.get("https://api-service:8443/api/data")
return response.json()
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
File: web-service/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY app.py .
# Run as non-root user
RUN useradd -m -u 1000 appuser
USER appuser
EXPOSE 8080
CMD ["python", "app.py"]
Step 3: Start Services
# Start all services
docker compose up -d
# Check status
docker compose ps
# Check logs
docker compose logs -f icp-agent
# Verify agent health
curl http://localhost:8080/healthz
Expected output:
{"status":"healthy"}
Step 4: Register Workloads
# Set variables
export ICP_SERVER_URL="https://dev.api.authsec.dev/spiresvc"
export TENANT_ID="your-tenant-id-here"
export NODE_ID="docker-prod-host-01"
# Register web-service
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:label:app": "web-service",
"docker:label:env": "production"
},
"ttl": 3600
}'
# Register api-service
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:label:app": "api-service",
"docker:label:env": "production"
},
"ttl": 3600
}'
Step 5: Test
# Test web service
curl http://localhost:8080/
# Expected output:
# {
# "service": "web-service",
# "spiffe_id": "spiffe://your-trust-domain.example.com/workload/web-service"
# }
# Test mTLS communication
curl http://localhost:8080/call-api
Workload Registration
Understanding Selectors
Docker workloads are matched using container metadata.
Common Selector Strategies
Strategy 1: By Container Labels (Recommended)
Best for production deployments:
# Register by app label
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:label:app": "web-app"
}
}'
Strategy 2: By Multiple Labels
For environment-specific workloads:
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:label:app": "web-app",
"docker:label:env": "production",
"docker:label:version": "v1"
}
}'
Strategy 3: By Container Name
For simple deployments:
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:container-name": "nginx-proxy"
}
}'
Strategy 4: By Image Name
For workloads with specific images:
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"docker:image-name": "postgres:14-alpine"
}
}'
Multi-Service Example
Complete docker-compose.yml with multiple services:
version: '3.8'
services:
icp-agent:
# ... (agent config as above)
frontend:
image: my-registry.example.com/frontend:latest
labels:
- "app=frontend"
- "tier=presentation"
- "env=production"
environment:
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
- DOCKER_LABEL_APP=frontend
- DOCKER_LABEL_TIER=presentation
volumes:
- agent-socket:/run/spire/sockets:ro
networks:
- icp-network
backend:
image: my-registry.example.com/backend:latest
labels:
- "app=backend"
- "tier=business"
- "env=production"
environment:
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
- DOCKER_LABEL_APP=backend
- DOCKER_LABEL_TIER=business
volumes:
- agent-socket:/run/spire/sockets:ro
networks:
- icp-network
database:
image: postgres:14-alpine
labels:
- "app=postgres"
- "tier=data"
- "env=production"
environment:
- POSTGRES_PASSWORD=secure-password
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
- DOCKER_LABEL_APP=postgres
- DOCKER_LABEL_TIER=data
volumes:
- agent-socket:/run/spire/sockets:ro
- postgres-data:/var/lib/postgresql/data
networks:
- icp-network
volumes:
agent-socket:
postgres-data:
networks:
icp-network:
Register all services:
# Frontend
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-d '{"spiffe_id": "spiffe://your-spiffe-id","parent_id": "spiffe://your-parent-id", "selectors": {"docker:label:app": "frontend"}}'
# Backend
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-d '{"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id", "selectors": {"docker:label:app": "backend"}}'
# Database
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-d '{"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id", "selectors": {"docker:label:app": "postgres"}}'
SDK Integration
Installation in Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install SDK from GitHub
RUN pip install git+https://github.com/authsec-ai/sdk-authsec.git
# Install application dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application
COPY . .
CMD ["python", "app.py"]
Quick Start Example
import asyncio
from authsec_sdk import QuickStartSVID
async def main():
# Initialize SDK
svid = await QuickStartSVID.initialize()
print(f"✅ Container authenticated as: {svid.spiffe_id}")
print(f"📜 Certificate expires: {svid.expires_at}")
# Use in your application
# ...
if __name__ == "__main__":
asyncio.run(main())
Client-Side mTLS
from authsec_sdk import QuickStartSVID
import httpx
import asyncio
async def call_backend():
svid = await QuickStartSVID.initialize()
# Create fresh SSL context (handles cert renewal)
ssl_context = svid.create_ssl_context_for_client()
async with httpx.AsyncClient(verify=ssl_context) as client:
response = await client.post(
"https://backend:8443/api/endpoint",
json={"key": "value"}
)
return response.json()
if __name__ == "__main__":
result = asyncio.run(call_backend())
print(result)
Server-Side mTLS
from fastapi import FastAPI
from authsec_sdk import QuickStartSVID
import uvicorn
import asyncio
app = FastAPI()
svid = None
@app.on_event("startup")
async def startup():
global svid
svid = await QuickStartSVID.initialize()
@app.get("/api/endpoint")
async def endpoint():
return {"status": "success"}
if __name__ == "__main__":
# Run with mTLS
uvicorn.run(
app,
host="0.0.0.0",
port=8443,
ssl_certfile=svid.cert_file_path,
ssl_keyfile=svid.key_file_path,
ssl_ca_certs=svid.ca_file_path
)
Production Deployment
Docker Swarm
For production scale-out:
version: '3.8'
services:
icp-agent:
image: your-docker-registry.example.com/icp-agent:latest
deploy:
mode: global # Run on every node
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.1'
memory: 128M
environment:
- ICP_AGENT_AGENT__TENANT_ID=your-tenant-id-here
- ICP_AGENT_AGENT__NODE_ID={{.Node.Hostname}}
- ICP_AGENT_ICP_SERVICE__ADDRESS=https://dev.api.authsec.dev/spiresvc
volumes:
- agent-socket:/run/spire/sockets
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- icp-network
web-service:
image: my-registry.example.com/web-service:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
restart_policy:
condition: on-failure
environment:
- SPIFFE_ENDPOINT_SOCKET=unix:///run/spire/sockets/agent.sock
volumes:
- agent-socket:/run/spire/sockets:ro
labels:
- "app=web-service"
- "env=production"
networks:
- icp-network
volumes:
agent-socket:
networks:
icp-network:
driver: overlay
Deploy:
docker stack deploy -c docker-compose.yml icp-stack
Monitoring
Prometheus
services:
prometheus:
image: prom/prometheus:latest
command:
- '--config.file=/etc/prometheus/prometheus.yml'
configs:
- source: prometheus-config
target: /etc/prometheus/prometheus.yml
networks:
- icp-network
configs:
prometheus-config:
file: ./prometheus.yml
File: prometheus.yml
scrape_configs:
- job_name: 'icp-agent'
static_configs:
- targets: ['icp-agent:8080']
Log Aggregation
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
networks:
- icp-network
promtail:
image: grafana/promtail:latest
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/log:/var/log:ro
command: -config.file=/etc/promtail/config.yml
networks:
- icp-network
Backup and Recovery
# Backup agent SVID cache
docker exec icp-agent tar czf - /var/lib/icp-agent > agent-backup.tar.gz
# Restore
docker exec -i icp-agent tar xzf - -C / < agent-backup.tar.gz
docker restart icp-agent
Troubleshooting
Issue 1: Agent Can't Access Docker API
Symptoms:
Error: permission denied while connecting to Docker daemon
Solution:
# Check socket mount
docker inspect icp-agent | grep -A 5 "Mounts"
# Fix permissions
sudo chmod 666 /var/run/docker.sock
# Or add agent to docker group (in Dockerfile)
RUN usermod -aG docker icp-agent
Issue 2: Workload Can't Connect to Agent
Symptoms:
Error: connect: no such file or directory
Solution:
# Check volume exists
docker volume ls | grep agent-socket
# Check volume is mounted
docker inspect web-service | grep -A 10 "Mounts"
# Recreate volume
docker volume rm agent-socket
docker compose up -d
Issue 3: Wrong SPIFFE ID Issued
# Check collected selectors (agent logs)
docker compose logs icp-agent | grep "Collected selectors"
# Check container labels
docker inspect web-service --format '{{json .Config.Labels}}' | jq
# Check registered workload entries
curl "${ICP_SERVER_URL}/api/v1/workloads?parent_id=spiffe://${TENANT_ID}/agent/${NODE_ID}"
Best Practices
1. Use Labels for Workload Identification
Always add descriptive labels:
labels:
- "app=web-service"
- "env=production"
- "version=v1.2.3"
- "team=backend"
- "criticality=high"
2. Implement Health Checks
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
3. Use Resource Limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
4. Implement Graceful Shutdown
import signal
import sys
def signal_handler(sig, frame):
print('Shutting down gracefully...')
# Cleanup code here
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
5. Use Secrets Management
services:
web-service:
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Next Steps
- Kubernetes Migration: See Kubernetes Integration Guide
Questions? Contact support@authsec.dev