Axion uses a multi-stage Docker pipeline to produce compiled, per-service
container images. The pipeline compiles all Python factory modules to
Cython .so files, trims the config to the target package, and produces
thin per-service images that share a common base.
Before building, ensure:
data.config/{package}_private.pem and data.config/{package}_public.pemdatabase section# Full pipeline (validate → compile → build image → push → scaffold)
python factory.deploy/ObjBuild.py pipeline homechoice
# Skip compile (use cached layers)
python factory.deploy/ObjBuild.py pipeline homechoice --skip-compile
# Build only (no push/deploy)
python factory.deploy/ObjBuild.py pipeline homechoice --skip-push --skip-deploy
# Build and email report on completion
python factory.deploy/ObjBuild.py pipeline homechoice --notify user@example.com
# Cross-build for ARM64 via buildx
python factory.deploy/ObjBuild.py pipeline homechoice --platform linux/arm64
# Multi-arch manifest (amd64 + arm64)
python factory.deploy/ObjBuild.py pipeline homechoice --platform linux/amd64,linux/arm64
# Base runtime image (dispatcher entrypoint)
docker build \
--target runtime \
-t axion-homechoice:latest \
--build-arg AXION_PACKAGE=homechoice \
-f resource.docker/dockerfile/homechoice.dockerfile .
# Per-service image (fixed entrypoint, no AXION_SERVICE needed)
docker build \
--target report \
-t axion-homechoice:report \
--build-arg AXION_PACKAGE=homechoice \
-f resource.docker/dockerfile/homechoice.dockerfile .
| Target | Service | Default Port | Command |
|---|---|---|---|
runtime |
Dispatcher | — | Reads AXION_SERVICE env |
report |
ServeReport | 9400 | python ServeReport.py --port 9400 |
webhook |
ServeWebHook | 9500 | python ServeWebHook.py serve 9500 |
web |
ServeWebsite | 9000 | python ServeWebsite.py 9000 |
workflow |
ServeWorkflow | — | python ServeWorkflow.py dispatcher |
go |
ServeGo | — | python ServeGo.py |
scheduler |
ServeScheduler | — | python ServeScheduler.py service |
mqtt |
ServeMqtt | — | python ServeMqtt.py main |
import |
ServeImport | — | python ServeImport.py watch |
conversation |
ServeConversation | 9550 | python ServeConversation.py serve |
ai |
ServeAI | 9700 | python ServeAI.py main --port 9700 |
monitor |
ServeMonitor | — | python ServeMonitor.py |
sms |
ServeSms | — | python ServeSms.py |
| Arg | Default | Purpose |
|---|---|---|
AXION_PACKAGE |
homechoice |
Target package name |
BASE_IMAGE |
python:3.12-bookworm |
Builder base image |
BASE_RUNTIME_IMAGE |
python:3.12-slim-bookworm |
Runtime base image |
REQUIREMENTS_FILE |
resource.config/requirements.txt |
Core pip requirements |
REQUIREMENTS_AI_FILE |
"" |
Set to yes to include AI/GPU deps |
AXION_AI_ENABLED |
0 |
Set to 1 for AI image variant |
SYSTEM_BUILD_DEPS |
(see Dockerfile) | apt packages for build stage |
SYSTEM_RUNTIME_DEPS |
(see Dockerfile) | apt packages for runtime |
uv (10-100x faster than pip)requirements.txt (core) + optionally requirements-ai.txtObjConfig.trimObjCompile.py all --offline (Cython compilation, no DB needed)factory.* with compiled .so outputpython:3.12-slim-bookworm baseAXION_PACKAGE, DOCKER_AXION, etc.)These directories are never baked into the image. Mount them at runtime:
| Mount Point | Purpose | k8s Source |
|---|---|---|
/axion/config.yaml |
Package configuration | ConfigMap |
/axion/data.config/ |
PEM encryption keys | Secret |
/axion/local.documents/ |
Runtime documents | PVC |
/axion/data.documents/ |
Data documents | PVC |
/axion/archive.documents/ |
Archive | PVC |
# With mounted config and PEM keys
docker run -d --name axion-report -p 9400:9400 \
-v /path/to/config.yaml:/axion/config.yaml:ro \
-v /path/to/data.config:/axion/data.config:ro \
axion-homechoice:report
# Using env overrides for database credentials
docker run -d --name axion-report -p 9400:9400 \
-e AXION_DATABASE_PASSWORD=secret \
-e AXION_DATABASE_HOST=db.example.com \
axion-homechoice:report
ConfigIni.Get(section, option) resolves in order:
AXION_{SECTION}_{OPTION} environment variable# HTML dashboard
curl http://host:9400/report/status
# PNG screenshot
curl -o status.png http://host:9400/report/status?format=png
# JSON test harness (87 assertions)
curl http://host:9400/report/status?format=json | jq '.tests.summary'
# CI gate script
result=$(curl -s http://host:9400/report/status?format=json)
pass_rate=$(echo "$result" | jq '.tests.summary.pass_rate')
if [ "$pass_rate" -lt 90 ]; then
echo "FAIL: $pass_rate%"
echo "$result" | jq '.tests.assertions[] | select(.pass == false)'
exit 1
fi
For services that need torch/nvidia/chromadb:
docker build \
--target ai \
-t axion-homechoice:ai \
--build-arg AXION_PACKAGE=homechoice \
--build-arg REQUIREMENTS_AI_FILE=yes \
--build-arg AXION_AI_ENABLED=1 \
-f resource.docker/dockerfile/homechoice.dockerfile .
This adds ~4.5GB (torch + nvidia CUDA + triton).
docker build \
-t axion-caddy:homechoice \
--build-arg AXION_PACKAGE=homechoice \
-f resource.docker/dockerfile/caddy.dockerfile .
62MB image. Configure via environment variables:
| Env | Default | Purpose |
|---|---|---|
CADDY_DOMAIN |
localhost |
Public domain (triggers TLS) |
ACME_EMAIL |
admin@technocore.co.za |
Let's Encrypt email |
UPSTREAM_REPORT |
report:9400 |
Report service upstream |
UPSTREAM_WEB |
web:9000 |
Web service upstream |
UPSTREAM_WEBHOOK |
webhook:9500 |
Webhook upstream |
Every pipeline run is recorded to MySQL. Query results via SQL or the
build report email (--notify).
| Table | Purpose |
|---|---|
log_build_run |
One row per pipeline execution — GUID, status, timing, host arch/OS |
log_build_step |
One row per step (config, compile, image, push, etc.) with timing |
log_build_compile |
One row per compiled module — pass/fail, error detail |
Tables are defined in factory.deploy/ObjBuild.yaml and auto-created
on first use.
# View recent builds
mysql -e "SELECT BuildGuid, Status, ElapsedSeconds, Platform
FROM log_build_run ORDER BY StartTime DESC LIMIT 5"
# View compile failures for a build
mysql -e "SELECT FactorySet, Module, CompileError
FROM log_build_compile
WHERE BuildGuid = '<guid>' AND CompileSuccess = 'N'"
The email report (--notify) includes KPI cards, step timeline,
compilation matrix with per-factory-set pass rates, and a live
registry overview. When --skip-compile is used, the report pulls
compile data from the most recent build that compiled.
The pipeline supports cross-building via Docker buildx. A remote
builder at 10.0.10.5 (LXC 136, 24 cores / 64GB) handles ARM64
builds via QEMU emulation.
# ARM64 only
python factory.deploy/ObjBuild.py pipeline homechoice --platform linux/arm64
# Multi-arch (amd64 + arm64)
python factory.deploy/ObjBuild.py pipeline homechoice \
--platform linux/amd64,linux/arm64
When --platform is set, the build uses docker buildx build --push
which pushes directly to the registry. The push step is automatically
skipped since buildx handles it.
Some packages don't have pre-built ARM wheels (e.g. libsql-experimental).
These are listed in resource.config/requirements-arm-exclude.txt and
stripped from requirements on non-amd64 architectures. The excluded
packages must have lazy imports in the application code.
deployment:
platform: "" # empty = native, or "linux/arm64"
buildx:
builder: "remote-builder" # buildx builder name
endpoint: "ssh://root@10.0.10.5" # remote builder SSH
insecure_registry: true
# Create the remote builder (already done)
docker buildx create --name remote-builder \
--driver docker-container \
ssh://root@10.0.10.5
# Verify platforms
docker buildx inspect remote-builder
A Python file failed Cython compilation. Check the compile error table
in the build output. Common causes:
Provision encryption keys first:
from ObjEncryption import ObjEncryption
e = ObjEncryption()
e.provision("homechoice")
Frame introspection in ObjDebug — fixed with safe getattr traversal.
Rebuild to pick up the fix.
Verify resource.web/ exists in the image and WebServer.py static
mounts are registered.
The Axion private registry runs on LXC 136 (registry) at
registry.technocore.co.za (HTTPS via Caddy on anchor).
LAN fallback: http://10.0.10.5:5000 (insecure, local only).
# Tag and push (pipeline does this automatically)
docker tag axion-homechoice:report registry.technocore.co.za/axion/homechoice-report:latest
docker push registry.technocore.co.za/axion/homechoice-report:latest
# Verify
curl -s https://registry.technocore.co.za/v2/_catalog
See resource.notes/howto/k3s-docker-registry-setup.md for full
registry setup, WireGuard networking, and K3s integration.
The build pipeline can generate deployment files for any substrate:
# Docker compose + Caddy (default)
python factory.deploy/ObjBuild.py scaffold homechoice
# K3s Helm chart + Caddy
AXION_DEPLOYMENT_SUBSTRATE=k3s python factory.deploy/ObjBuild.py scaffold homechoice
# Supervisor conf + Caddy
AXION_DEPLOYMENT_SUBSTRATE=supervisor python factory.deploy/ObjBuild.py scaffold homechoice
resource.docker/dockerfile/homechoice.dockerfile — main Dockerfileresource.docker/dockerfile/caddy.dockerfile — Caddy proxyresource.docker/entrypoint.sh — service dispatcherresource.docker/caddyfile/homechoice.Caddyfile — routing config (manual)resource.docker/caddyfile/homechoice.generated.Caddyfile — routing config (generated)resource.docker/helm/homechoice/ — K3s Helm chart (generated)factory.deploy/ObjBuild.py — build pipeline orchestratorfactory.deploy/extend.runner/ — substrate-specific build runnersfactory.core/extend.environment/ — runtime runners (start/stop/restart)factory.report/package.system/ObjReportStatus.py — status dashboardresource.notes/howto/k3s-docker-registry-setup.md — infrastructure setup guideresource.notes/howto/deployment_architecture.md — architecture diagrams