Docker Deployment Guide
Deploy the Pyvorin Edge Agent with Docker and Docker Compose, including multi-stage builds, health checks, secrets management, and ARM64 cross-compilation.
Published Jun 2, 2026
Introduction
Containerising the EdgeAgent brings reproducible builds, dependency isolation, and simplified rollback. This article covers a multi-stage Dockerfile, a production-ready docker-compose.yml, health checks, secrets handling, and ARM64 builds for Raspberry Pi and other edge hardware.
Multi-Stage Dockerfile
The Dockerfile below uses Python 3.12-slim as the base, copies the Edge SDK and runtime, installs dependencies, and runs the agent as a non-root user. It is optimised for layer caching: dependencies are installed before source code changes.
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
# Install build dependencies for cryptography and any native wheels
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libffi-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Copy and install Python dependencies first (layer cache)
COPY edge_sdk/requirements.txt edge_sdk/requirements.txt
COPY edge_runtime/requirements.txt edge_runtime/requirements.txt
RUN pip install --user --no-cache-dir -r edge_sdk/requirements.txt \
&& pip install --user --no-cache-dir -r edge_runtime/requirements.txt
# --- Runtime stage ---
FROM python:3.12-slim AS runtime
# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
sqlite3 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r pyvorin && useradd -r -g pyvorin -m -d /app pyvorin
# Copy installed packages from builder
COPY --from=builder /root/.local /home/pyvorin/.local
ENV PATH=/home/pyvorin/.local/bin:$PATH
# Copy application code
COPY --chown=pyvorin:pyvorin edge_sdk /app/edge_sdk
COPY --chown=pyvorin:pyvorin edge_runtime /app/edge_runtime
# Set PYTHONPATH so imports resolve
ENV PYTHONPATH=/app/edge_sdk:/app/edge_runtime
WORKDIR /app
# Health endpoint exposed by EdgeAgent (default 8080)
EXPOSE 8080
# Health check — matches the /health route in main.py _HealthHandler
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
USER pyvorin
ENTRYPOINT ["python", "-m", "pyv_edge_agent"]
CMD ["--config", "/app/config/config.toml"]
Docker Compose Configuration
The compose file mounts volumes for persistent SQLite data and bind-mounts the config file for easy editing without rebuilding the image.
version: "3.8"
services:
edge-agent:
build:
context: ..
dockerfile: deploy/Dockerfile
image: pyvorin/edge-agent:latest
container_name: pyvorin-edge
restart: unless-stopped
# Volume mounts
volumes:
# Persistent SQLite databases and backups
- edge-data:/app/data
# Bind-mount config for live edits
- ./config:/app/config:ro
# Optional: mount device-tree for thermal reads inside container
- /sys/class/thermal:/sys/class/thermal:ro
# Port mapping (host -> container)
ports:
- "127.0.0.1:8080:8080"
# Resource limits
deploy:
resources:
limits:
cpus: '0.8'
memory: 512M
reservations:
cpus: '0.2'
memory: 128M
# Environment variables (non-sensitive)
environment:
- PYTHONUNBUFFERED=1
- LOG_LEVEL=INFO
# Secrets (sensitive)
secrets:
- api_key
- mqtt_password
# Network mode options discussed below
networks:
- edge-net
# Logging
logging:
driver: "json-file"
options:
max-size: "32m"
max-file: "5"
volumes:
edge-data:
driver: local
secrets:
api_key:
file: ./secrets/api_key.txt
mqtt_password:
file: ./secrets/mqtt_password.txt
networks:
edge-net:
driver: bridge
Consuming Secrets Inside the Container
Docker secrets are mounted as files under /run/secrets/. The EdgeAgent Config class in /var/www/pyvorin/edge_runtime/pyv_edge_agent/config.py supports environment variable substitution with ${VAR} and ${VAR:-default}. You can combine this with an entrypoint script that reads secret files into env vars:
#!/bin/sh
# /app/docker-entrypoint.sh — run inside container
if [ -r /run/secrets/api_key ]; then
export PYV_API_KEY=$(cat /run/secrets/api_key)
fi
if [ -r /run/secrets/mqtt_password ]; then
export PYV_MQTT_PASSWORD=$(cat /run/secrets/mqtt_password)
fi
exec python -m pyv_edge_agent --config /app/config/config.toml "$@"
Then reference the variables in config.toml:
[cloud]
enabled = true
endpoint = "https://api.pyvorin.com/v1/ingest"
api_key = "${PYV_API_KEY}"
[ingest.mqtt]
host = "mqtt.broker.local"
port = 8883
password = "${PYV_MQTT_PASSWORD}"
Network Mode: Host vs Bridge
Choosing the correct Docker network mode is critical for MQTT and multicast discovery.
| Mode | Pros | Cons | When to Use |
|---|---|---|---|
bridge |
Port isolation, compose-friendly, firewall rules apply | Extra NAT overhead, multicast broken | Cloud-only ingest, HTTP adapters |
host |
Zero NAT overhead, multicast works, MQTT broker on same host visible | No port isolation, conflicts if multiple agents run | MQTT ingest, local multicast discovery |
To use host networking, change the compose service:
services:
edge-agent:
# ...
network_mode: host
# Remove ports: and networks: when using host mode
Building for ARM64
Most edge devices — Raspberry Pi 3/4/5, NVIDIA Jetson, Orange Pi — run ARM64 (aarch64). Docker Buildx makes cross-compilation straightforward from an x86_64 workstation.
One-Time Buildx Setup
# Create a buildx builder with QEMU support
docker buildx create --name edge-builder --use --bootstrap
# Verify platforms
docker buildx inspect --bootstrap
# Look for: linux/amd64, linux/arm64, linux/arm/v7
Multi-Platform Build
# Build and push to registry
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.pyvorin.com/edge-agent:1.4.2 \
-f deploy/Dockerfile \
--push \
.
# Build and load locally (single platform only)
docker buildx build \
--platform linux/arm64 \
-t pyvorin/edge-agent:local-arm64 \
-f deploy/Dockerfile \
--load \
.
Running on ARM64
# On the edge device (Raspberry Pi 4, ARM64)
docker pull registry.pyvorin.com/edge-agent:1.4.2
# Verify architecture
docker inspect registry.pyvorin.com/edge-agent:1.4.2 \
--format='{{.Os}}/{{.Architecture}}'
# Should print: linux/arm64
docker-compose up -d
Complete Working Example
Below is a minimal, copy-pasteable project layout for a Docker-based Edge deployment.
Directory Layout
pyvorin-edge-deploy/
├── docker-compose.yml
├── Dockerfile
├── docker-entrypoint.sh
├── config/
│ └── config.toml
└── secrets/
├── api_key.txt
└── mqtt_password.txt
Dockerfile (Single File, No Multi-Stage)
For simpler deployments where image size is less critical:
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libffi-dev libssl-dev sqlite3 curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy source
COPY edge_sdk /app/edge_sdk
COPY edge_runtime /app/edge_runtime
ENV PYTHONPATH=/app/edge_sdk:/app/edge_runtime
# Entrypoint
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["docker-entrypoint.sh"]
docker-compose.yml (Complete)
version: "3.8"
services:
edge-agent:
build: .
container_name: pyvorin-edge
restart: unless-stopped
volumes:
- edge-data:/app/data
- ./config:/app/config:ro
ports:
- "127.0.0.1:8080:8080"
environment:
- PYTHONUNBUFFERED=1
- LOG_LEVEL=INFO
secrets:
- api_key
- mqtt_password
deploy:
resources:
limits:
cpus: '0.8'
memory: 512M
logging:
driver: "json-file"
options:
max-size: "32m"
max-file: "5"
volumes:
edge-data:
secrets:
api_key:
file: ./secrets/api_key.txt
mqtt_password:
file: ./secrets/mqtt_password.txt
Operational Commands
# Start
docker-compose up -d
# View logs
docker-compose logs -f edge-agent
# Restart after config change
docker-compose restart edge-agent
# Shell into running container for debugging
docker-compose exec edge-agent /bin/bash
# Backup SQLite databases from volume
docker-compose exec edge-agent sqlite3 /app/data/edge_store.db ".backup /app/data/edge_store_backup.db"
docker cp pyvorin-edge:/app/data/edge_store_backup.db ./backups/
# Update to new image
docker-compose pull
docker-compose up -d
Summary
Docker containerisation adds reproducibility and operational safety to EdgeAgent deployments. Use multi-stage builds to keep images small, Docker secrets for credentials, bridge networking for HTTP-only ingest, and host networking for MQTT. Build for ARM64 with docker buildx so your CI pipeline on x86_64 produces images ready for Raspberry Pi. Always configure a HEALTHCHECK so orchestrators can detect and restart failed agents automatically.