edge Advanced 24 min read

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.