Containerize Apps for ALPON with Dockerfile Templates

This guide is designed to help you quickly and easily containerize your applications on the ALPON devices.

Tutorial · Docker · ARM64

Containerize Apps for ALPON with Dockerfile Templates

Package your application as an ARM64 Docker image that runs on both ALPON X5 AI and ALPON X4. This guide walks through preparing your project, writing the Dockerfile, and using two ready-made templates (Debian for development, Alpine for production) so the resulting image deploys cleanly through Sixfab Connect.

ALPON X5 AI ALPON X4 Docker · ARM64 Dockerfile Templates
ALPON · Tutorial · App Containerization · Shared (X4 + X5 AI)
How do I containerize an application for ALPON?

Place your source, dependencies, and a .dockerignore in one project folder, then write a Dockerfile that picks an ARM64 base image, sets a working directory, copies your code, installs dependencies, and declares the startup command. Build for ARM64 with docker buildx build --platform linux/arm64, then push the image to the Sixfab Container Registry at cr.sixfab.io. From there, deploy it to any ALPON X5 AI or ALPON X4 through Sixfab Connect.

Build for ARM64, not x86

Both ALPON X5 AI (Raspberry Pi CM5 · Cortex-A76) and ALPON X4 (Raspberry Pi CM4 · Cortex-A72) run on ARM64. Every image you deploy must include a linux/arm64 variant. Most official images on Docker Hub (nginx, redis, postgres, node, python) already ship multi-arch manifests. Custom images built on an x86 workstation must use docker buildx build --platform linux/arm64 — a plain docker build on a Mac or PC will produce an x86 image that fails to start on ALPON with exec format error.

Why ALPON for containerization

Every ALPON device ships with a Docker runtime preinstalled and authenticated against the Sixfab Container Registry out of the box. There is no SSH setup, no per-device credential rotation, and no manual docker pull. The two devices in the line share the same ARM64 build target, so a single image works on both:

ALPON X5 AI Raspberry Pi CM5 · Cortex-A76 · 4-core ARM64 · 8 GB RAM. Adds the DEEPX DX-M1 NPU for AI workloads.
ALPON X4 Raspberry Pi CM4 · Cortex-A72 · 4-core ARM64 · 8 GB RAM. General-purpose IoT and edge compute.
Docker preinstalled Runtime ready on first boot. No apt install docker, no daemon config.
One image, two devices A single linux/arm64 image runs on both ALPON X5 AI and ALPON X4 without changes.

Traditional vs. containerized application management

In a traditional setup each application sits on its own OS or virtual machine, brings its own dependencies, and is updated through manual scp + systemctl rituals. Scaling means provisioning more hosts; debugging means dialling into individual devices. On a fleet of edge devices that pattern collapses fast.

Containers flip the model. The application and every dependency it needs are baked into one immutable image. The same image runs identically on every device, deployments take seconds, and rollbacks are a tag change — not a hot-fix shell session. For ALPON fleets, that is the only model we recommend.

Concern Traditional Containerized on ALPON
Dependencies Installed per device, drift over time Frozen in the image, identical on every device
Deployment Manual file transfer, restart scripts One-click from Sixfab Connect, seconds per device
Rollback Reinstall older binaries, debug breakage Redeploy previous image tag, instant
Isolation Shared user-space, dependency conflicts Each container runs in its own namespace
Fleet update SSH loop across devices Push new tag, deploy from Connect

Prepare the project for containerization

Before writing the Dockerfile, get the project folder in shape. The image build process copies whatever is in this directory into the container, so anything that belongs out should be excluded explicitly.

  1. 1

    List every dependency

    Every library, runtime, and system tool the app needs must be declared somewhere the build can read. Use the convention for your language:

    • Pythonrequirements.txt (or pyproject.toml for Poetry/uv).
    • Node.jspackage.json + package-lock.json.
    • Gogo.mod + go.sum.
    • System packages — list them in the Dockerfile RUN apt-get install (Debian) or RUN apk add (Alpine) line.

    A reference Python project layout:

    project layout tree
    my_app/
    ├── app.py                 # Main application entrypoint
    ├── requirements.txt       # Python dependencies
    ├── Dockerfile
    └── .dockerignore          # Files excluded from the image
  2. 2

    Add a .dockerignore file

    Without a .dockerignore, every log file, secret, and IDE folder gets copied into the image. That bloats the size, slows pulls over cellular, and can leak credentials. Treat it like a .gitignore for the build context:

    .dockerignore ignore
    # Logs and runtime artefacts
    *.log
    *.pyc
    __pycache__/
    
    # Tests and dev tooling
    tests/
    .vscode/
    .idea/
    
    # Secrets and local config
    .env
    .env.*
    
    # Version control
    .git/
    .gitignore
  3. 3

    Identify the runtime surface

    Decide upfront what the container needs from the host. These three answers shape the deployment form on Sixfab Connect later:

    • Environment variables — anything that changes between dev and prod (database URLs, API keys, feature flags). Never hard-code these in the source.
    • Ports — which TCP/UDP port the app listens on inside the container. On ALPON, the host port must sit in the 30000–32767 range; the container-side port is free.
    • Volumes — directories the container needs to read or write on the host (persistent storage, device files, configs). Plan these as Docker volume mounts, not bind mounts to specific host paths.

Write the Dockerfile

The Dockerfile is the recipe for the image. Each instruction creates a new image layer; ordered correctly, it lets Docker cache the slow steps and rebuild only what changed. The five instructions below cover the vast majority of edge applications.

Instruction What it does
FROM Picks the base image. Always pin a tag (e.g. python:3.11-slim), never use latest in production.
WORKDIR Sets the working directory inside the container. Subsequent instructions run here.
COPY Copies files from the build context into the image. Copy requirements.txt before the rest of the source to maximise cache reuse.
RUN Executes a shell command at build time. Use it to install system packages and language dependencies.
CMD Defines the command that runs when the container starts. Always exec-form: CMD ["python", "app.py"].

A minimal Python Dockerfile, walked through

Pick the base image. The -slim variants are far smaller than the default and still include enough to install most pure-Python packages:

Dockerfile dockerfile
# Pinned, slim, ARM64-compatible Python runtime
FROM python:3.11-slim

Set the working directory. Every COPY and RUN that follows resolves relative to it:

Dockerfile dockerfile
WORKDIR /app

Copy requirements.txt first, install, then copy the rest of the source. This ordering means Docker only rebuilds the dependency layer when requirements.txt changes — code-only edits skip the slow pip install step entirely:

Dockerfile dockerfile
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

Declare the startup command. Use exec-form (the JSON array, not the shell string) so signals like SIGTERM reach the app cleanly when the container stops:

Dockerfile dockerfile
CMD ["python", "app.py"]

The complete Dockerfile

Combine the pieces above:

Dockerfile dockerfile
# Pinned, slim, ARM64-compatible Python runtime
FROM python:3.11-slim

# Working directory for the rest of the build
WORKDIR /app

# Install dependencies first — better cache reuse
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the source
COPY . .

# Start the application
CMD ["python", "app.py"]

Build it for ARM64

From the project root on your workstation:

terminal bash
# 1. Enable buildx (one-time, if not already)
docker buildx create --use

# 2. Build the ARM64 image and load it locally
docker buildx build --load --platform linux/arm64 -t my-app:latest .

Dockerfile templates

Two ready-made templates cover the two phases of work. Use the Debian template while iterating; switch to the Alpine template before shipping to a fleet.

Development · Debian For iteration

Built on debian:bookworm-slim. Includes the build toolchain and git so you can compile native modules, debug from inside the container, and tweak dependencies without rebuilding the base.

  • Includes build-essential, git
  • Familiar apt ecosystem
  • Larger image (~150–300 MB) — fine for dev
Production · Alpine For deployment

Built on alpine:latest. Stripped down to the minimum runtime, no build tools, no shell utilities you don't need. Pulls faster over cellular and presents a smaller attack surface.

  • Uses apk package manager
  • Tiny image (~5–50 MB typical)
  • Faster pulls, less to patch

Development Dockerfile (Debian-based)

Drop this in the project root during active development:

Dockerfile.dev dockerfile
# Development Dockerfile — Debian-based
FROM debian:bookworm-slim

WORKDIR /app

# Build toolchain and dev utilities
RUN apt-get update && apt-get install -y 
    build-essential 
    git 
    && rm -rf /var/lib/apt/lists/*

COPY . .

# Mark the runtime environment
ENV APP_ENV=development

# Replace with your dev entrypoint
CMD ["./your-dev-app"]

Production Dockerfile (Alpine-based)

Switch to this template once the app is stable. It optimises for image size, pull time, and the smaller attack surface that matters on a fleet of edge devices:

Dockerfile dockerfile
# Production Dockerfile — Alpine-based
FROM alpine:latest

WORKDIR /app

# Runtime-only dependencies, no build tools
RUN apk add --no-cache 
    libc6-compat 
    && rm -rf /var/cache/apk/*

COPY . .

# Mark the runtime environment
ENV APP_ENV=production

# Replace with your prod entrypoint
CMD ["./your-prod-app"]
Alpine and Debian use different package managers

Alpine uses apk; Debian uses apt. Package names also differ — Debian's libssl-dev is Alpine's openssl-dev; Debian's libpq-dev is Alpine's postgresql-dev. When porting from the dev template to prod, look up each apt package on pkgs.alpinelinux.org and replace it with the Alpine equivalent. Native C extensions built against glibc may also need libc6-compat on Alpine (musl).


Best practices for ALPON containers

The same image runs on both ALPON X5 AI and ALPON X4, but small choices in the Dockerfile decide whether it pulls in 10 seconds or 10 minutes over a cellular link, and whether it survives the next dependency update.

  • Pick small base images. Prefer -slim, -alpine, or distroless variants over the default full-fat tags. A 50 MB image pulls in seconds; a 1.2 GB image can take half an hour over cellular.
  • Pin every version. Use python:3.11-slim, not python:latest. Use nginx:1.27-alpine, not nginx:alpine. Pinned tags mean today's working build still works in three months.
  • Always include a .dockerignore. Strip out .git, logs, secrets, IDE folders, and test artefacts. A clean build context is faster to send to the daemon and produces smaller, safer images.
  • Order layers for cache reuse. Copy dependency manifests (requirements.txt, package.json) and install them before copying the rest of the source. Edits to app.py won't invalidate the dependency layer.
  • Combine RUN commands. Each RUN creates a layer. Chain related commands with && and clean up in the same line so removed files don't survive in the previous layer: RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*.
  • Use ENV for configuration, not source code. Database URLs, API keys, log levels — set them as environment variables on the deploy form. Hard-coded values mean a code change for every environment.
  • Build with buildx for ARM64. A plain docker build on an x86 Mac or PC produces an x86 image that fails to start on ALPON. Always include --platform linux/arm64.
  • Use exec-form CMD. Write CMD ["python", "app.py"], not CMD python app.py. Exec form passes signals (SIGTERM on stop) directly to your process so the container shuts down cleanly.

A polished, production-ready example

Applying the practices above to a real Dockerfile:

Dockerfile dockerfile
# Pinned, slim, ARM64 base
FROM python:3.11-slim

WORKDIR /app

# System packages and Python deps in one layer, with cleanup
RUN apt-get update && apt-get install -y --no-install-recommends 
    libpq5 
    && rm -rf /var/lib/apt/lists/*

# Dependencies first — cached unless requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Then the source
COPY . .

# Runtime configuration via ENV (override on the deploy form)
ENV APP_ENV=production 
    LOG_LEVEL=info

# Exec-form CMD — clean signal handling
CMD ["python", "app.py"]

Next steps: push and deploy

The image is built and tested locally. Two things remain: push it to the Sixfab Container Registry, then deploy it from Sixfab Connect. Both are covered end-to-end on the Deploy Applications guide, including the exact docker login + docker tag + docker push sequence and the deploy form fields (ports, environment variables, volumes, network mode).

Continue with Deploy Applications on ALPON