01

The Big Picture

Seven package ecosystems, one approval gate, one image builder. Here's how they fit together to give your nodes exactly the software they need — without ever touching the public internet.

Why internal mirrors?

Every Axiom node runs jobs inside a container built from a Node Image. That image needs Python packages, system libraries, container base layers, and sometimes npm modules, Conda packages, or PowerShell tooling. In production, you don't want every build pulling from pypi.org, Docker Hub, or npmjs.com — that's slow, unreliable, subject to rate limits, and a supply chain risk.

Internal mirrors solve all of this: packages are cached locally, builds are reproducible offline, and every package is explicitly approved before it enters your fleet.

The seven mirrors

Ecosystem Mirror Backend How It Caches
PyPI Python devpi Caching proxy — fetches on first request, serves from cache after
APT Debian / Ubuntu apt-cacher-ng Transparent proxy — intercepts apt-get automatically
apk Alpine nginx reverse cache HTTP cache proxy for dl-cdn.alpinelinux.org
OCI Docker / Container Images registry:2 pull-through Pull-through cache — proxies Docker Hub, GHCR, etc.
npm Node.js Verdaccio Caching proxy — mirrors npmjs.com on demand
Conda Data Science conda-mirror / HTTP cache Proactive mirror of named packages + versions
NuGet PowerShell BaGet Lightweight NuGet v3 server — packages pushed manually

The package lifecycle

Regardless of ecosystem, every package flows through the same controlled pipeline. Click Next Step to trace the path.

1
Approve

Smelter

2
Mirror

Auto-fetch

3
Scan

CVE check

4
Blueprint

Compose image

5
Build

Foundry

Click "Next Step" to trace the package lifecycle.
Step 0 / 5

One workflow, seven ecosystems

The operator experience is identical regardless of package type. In the Smelter Registry, you approve an ingredient — specifying its ecosystem (PyPI, APT, apk, OCI, npm, Conda, or NuGet), name, and version. The mirror service proactively fetches it using the appropriate backend. When you build a Node Image in the Foundry, only approved-and-mirrored packages are available.

💡
EE feature

The Smelter Registry, Foundry image builder, and BOM Explorer are Enterprise Edition features. The Admin page shows these tabs only when an EE licence is active.

02

Python Packages (PyPI)

Cache Python packages locally with devpi so image builds are fast, reproducible, and independent of pypi.org.

How devpi works

devpi is a caching proxy that sits between your image builds and pypi.org. The first time a package is requested, devpi fetches it from upstream and stores a local copy. Every subsequent request is served from cache.

It runs as a Docker container alongside the Axiom stack on port 3141.

Already running

If you deployed with the standard compose.server.yaml, devpi is already running. Check with: docker ps | grep devpi

One-time setup

If devpi isn't in your compose file yet, add this service:

compose.server.yaml
devpi:
  image: devpi/devpi-server:latest
  restart: unless-stopped
  ports:
    - "3141:3141"
  volumes:
    - devpi-data:/data
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3141/+api"]
    interval: 30s
    retries: 3

Configure the mirror URL

1
Open Admin → Smelter Registry

Navigate to /admin and click the Smelter Registry tab.

2
Set the PyPI mirror URL

In the Mirror Configuration section, enter http://devpi:3141/root/pypi/+simple/ and save.

⚠️
Common mistake: wrong URL path

The path must be /root/pypi/+simple/, not /+simple/. The root/pypi is devpi's default index name.

Adding Python packages

You don't add packages to devpi directly. Approve them in the Smelter Registry (Module 6) and the mirror service proactively runs pip download to cache them. When Foundry builds an image that includes those packages, it pulls from devpi — fast, cached, and verified.

For air-gapped environments, upload .whl files directly via the Smelter's upload button.

03

System Packages (APT & apk)

Cache Debian/Ubuntu and Alpine system packages so image builds don't depend on upstream archives.

APT — Debian & Ubuntu

apt-cacher-ng is a transparent caching proxy. All apt-get install calls during Foundry builds are routed through it. Packages cache on first fetch; subsequent builds are instant.

compose.server.yaml
apt-mirror:
  image: sameersbn/apt-cacher-ng:latest
  restart: unless-stopped
  ports:
    - "3142:3142"
  volumes:
    - apt-cache-data:/var/cache/apt-cacher-ng
  environment:
    - DISABLE_IPFORWARD_CHECK=1

Configure the URL in Admin → Smelter Registry → Mirror Config: http://apt-mirror:3142

Foundry injects a temporary APT proxy directive into every Dockerfile, then removes it from the final image so the config doesn't leak to deployed nodes.

⚠️
DISABLE_IPFORWARD_CHECK is required

Inside Docker, the IP forwarding check fails spuriously. Without this flag, apt-cacher-ng refuses to start.

apk — Alpine Linux

Alpine uses apk, not APT. There's no apt-cacher-ng equivalent, but an nginx reverse-proxy cache does the same job. It sits between your builds and dl-cdn.alpinelinux.org, caching .apk files on first fetch.

compose.server.yaml
apk-cache:
  image: nginx:alpine
  restart: unless-stopped
  ports:
    - "3143:80"
  volumes:
    - ./apk-cache.conf:/etc/nginx/conf.d/default.conf:ro
    - apk-cache-data:/var/cache/apk-proxy
apk-cache.conf
proxy_cache_path /var/cache/apk-proxy
    levels=1:2 keys_zone=apk:10m
    max_size=10g inactive=7d;

server {
    listen 80;
    location / {
        proxy_pass       http://dl-cdn.alpinelinux.org;
        proxy_cache      apk;
        proxy_cache_valid 200 7d;
        proxy_cache_use_stale error timeout updating;
    }
}

Foundry injects a temporary /etc/apk/repositories rewrite pointing at the cache during builds, then restores the default repos in the final image — same pattern as the APT proxy.

Proactive package fetching

Both APT and apk mirrors can also work proactively. When you approve a system package in the Smelter, the mirror service fetches it ahead of time:

DEBIAN Runs apt-get download <pkg> in a throwaway container to pre-warm the apt-cacher-ng cache
ALPINE Runs apk fetch <pkg> in a throwaway Alpine container to pre-warm the nginx cache

This means packages are already cached before any Foundry build runs — important for air-gapped environments where you pre-warm while you still have internet, then disconnect.

04

Container Base Images

Every Foundry build starts with FROM python:3.12-slim or FROM alpine:3.20. A local registry cache ensures those pulls never fail and never hit Docker Hub rate limits.

The problem with upstream registries

Docker Hub rate limits

Anonymous pulls: 100 per 6 hours. Authenticated: 200. A busy Foundry can hit this in minutes if images aren't cached.

🌐
Network dependency

If Docker Hub or GHCR is down, every Foundry build fails. In air-gapped environments, upstream pulls are impossible.

💾
Bandwidth waste

Base images are 50-200MB. Pulling the same image on every build wastes bandwidth and slows builds.

One-time setup: pull-through cache

Docker's official registry:2 image has built-in pull-through cache support. One environment variable turns it into a transparent proxy for Docker Hub:

compose.server.yaml
registry-cache:
  image: registry:2
  restart: unless-stopped
  ports:
    - "5000:5000"
  volumes:
    - registry-cache-data:/var/lib/registry
  environment:
    - REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io

To also cache GHCR images, you can add a second registry instance pointing at https://ghcr.io.

Proactive image caching

When you approve a container base image in the Smelter (ecosystem: OCI), the mirror service proactively pulls it:

1
Approve in Smelter

Name: python, Version: 3.12-slim, Ecosystem: OCI

2
Mirror service pulls the image

Runs docker pull python:3.12-slim through the pull-through cache, warming it locally. Status goes PENDING → MIRRORED.

3
Foundry builds from cache

When a blueprint uses python:3.12-slim as its base, the FROM pull hits the local registry — instant, no rate limits.

💡
Air-gapped deployments

Pre-warm the cache while you have internet. The registry-cache-data volume persists all cached layers. Disconnect the network and builds still work — every FROM resolves locally.

05

npm, Conda & PowerShell

Three more ecosystems for specialised workloads: Node.js jobs, data science pipelines, and Windows automation.

npm — Verdaccio

If your node images run JavaScript/TypeScript jobs, Verdaccio provides a lightweight npm registry that caches packages from npmjs.com on demand.

compose.server.yaml
verdaccio:
  image: verdaccio/verdaccio:5
  restart: unless-stopped
  ports:
    - "4873:4873"
  volumes:
    - verdaccio-data:/verdaccio/storage

Verdaccio works like devpi: first install caches the package, subsequent installs serve from local storage. When you approve an npm ingredient in the Smelter, the mirror service proactively runs npm pack <pkg>@<version> to pre-warm the cache.

In Foundry blueprints, set the npm registry in the Dockerfile: npm config set registry http://verdaccio:4873

Conda — Data Science packages

For data science workloads that depend on Conda packages (numpy, pandas, scikit-learn, etc.), you can mirror specific packages and versions locally.

Two approaches:

📦

conda-mirror

Mirrors named packages + versions from conda-forge or defaults channel to a local directory. Served by nginx or Python's http.server. Best for production with a defined package set.

🚀

nginx HTTP cache

Same pattern as the apk mirror — nginx reverse-proxies conda.anaconda.org and caches on first fetch. Simpler setup, but doesn't proactively warm.

When you approve a Conda ingredient in the Smelter, the mirror service runs conda create --download-only -n tmp <pkg>=<version> in a throwaway container to fetch the package into the local mirror. The Foundry injects a .condarc pointing at the local mirror during builds.

⚠️
Anaconda Terms of Service

Packages from Anaconda's default channel (repo.anaconda.com) require a paid commercial licence for organisations with 200+ employees. This is a usage restriction from Anaconda, Inc. — not a code licence issue.

Recommended alternative: Use conda-forge (conda-forge.org) as your upstream channel instead. conda-forge is community-maintained, has no commercial restrictions, and covers the vast majority of data science packages. When approving Conda ingredients in the Smelter, you can choose which channel to mirror from — the Foundry will flag the Anaconda ToS if you select the default channel.

PowerShell — BaGet

For Windows automation jobs that need PowerShell modules (Pester, PSScriptAnalyzer, custom internal modules), BaGet provides a lightweight NuGet v3 server.

compose.server.yaml
baget:
  image: loicsharma/baget:latest
  restart: unless-stopped
  ports:
    - "5555:80"
  volumes:
    - baget-data:/var/baget
  environment:
    - ApiKey=AXIOM-INTERNAL-NUGET-KEY
    - Storage__Type=FileSystem
    - Storage__Path=/var/baget
    - Database__Type=Sqlite
    - Database__ConnectionString=Data Source=/var/baget/db/baget.db
    - Search__Type=Database

BaGet requires packages to be pushed to it. When you approve a NuGet ingredient in the Smelter, the mirror service downloads the .nupkg from PSGallery and pushes it to BaGet automatically. You can also upload .nupkg files manually via the Smelter upload button.

In Foundry tool recipes, register the repository:

injection recipe
RUN pwsh -Command "Register-PSRepository \
  -Name 'AxiomInternal' \
  -SourceLocation 'http://baget:5555/v3/index.json' \
  -InstallationPolicy Trusted"
⚠️
Change the API key

AXIOM-INTERNAL-NUGET-KEY is an example. Use a strong, unique key in production.

06

Smelter — The Unified Approval Gate

One dashboard, seven ecosystems. Approve packages, watch them mirror, scan for vulnerabilities, enforce compliance.

What the Smelter gives you

Supply chain control

Only explicitly approved packages — across all seven ecosystems — can be built into images. No surprise dependencies.

🔍
CVE scanning

One-click vulnerability scan using pip-audit. Vulnerable packages get a red badge.

📡
Mirror status tracking

See at a glance which packages are cached locally (MIRRORED), still downloading (PENDING), or failed (FAILED).

🔒
Enforcement

STRICT mode blocks builds with unapproved packages. WARNING mode allows them but marks the image non-compliant.

Approving a package (any ecosystem)

The workflow is identical whether you're approving a Python package, an Alpine system library, or a Docker base image.

1
Open Admin → Smelter Registry

Navigate to /admin and click the Smelter Registry tab.

2
Click "Add Ingredient"

Fill in: Package Name, Version, Ecosystem (PyPI, APT, apk, OCI, npm, Conda, or NuGet), and optionally a SHA256 hash.

3
Watch the mirror status

The ingredient appears with a PENDING badge. The mirror service picks the right backend and proactively fetches the package. Within seconds, the badge changes to MIRRORED.

That's it

The package is now approved and cached in the appropriate mirror backend. It can be included in any Foundry blueprint. If the download fails, the badge shows FAILED — expand the row to see the mirror log.

Proactive fetch: how each ecosystem mirrors

Ecosystem Fetch Command Runs In
PyPI pip download <pkg> Host (direct)
APT apt-get download <pkg> Throwaway Debian container
apk apk fetch <pkg> Throwaway Alpine container
OCI docker pull <image>:<tag> Host (through pull-through cache)
npm npm pack <pkg>@<ver> Throwaway Node container
Conda conda create --download-only Throwaway Conda container
NuGet curl nupkg + push to BaGet Host (direct)

Air-gapped uploads

If your environment has no internet, upload package files directly via the Smelter. Every ingredient row has an upload button. Upload the file (.whl, .deb, .apk, .nupkg, .tar.gz, .tgz) and the mirror status changes to MIRRORED immediately.

Enforcement modes

🛑

STRICT

Builds with unapproved or unmirrored packages are rejected (HTTP 403). Use this in production.

WARNING

Builds proceed but the template is marked non-compliant (amber badge). Use this during development.

Switch modes via the enforcement dropdown at the top of the Smelter Registry tab.

07

Foundry — Building Node Images

Combine approved packages from any ecosystem into a custom Docker image. The Foundry's guided wizard handles the Dockerfile — you never write one by hand.

The three building blocks

📋
Runtime Image Recipe (Blueprint)

Defines what goes in the image: base OS, Python/npm/Conda packages, system packages, and tools.

🌐
Network Image Recipe (Blueprint)

Defines egress rules — what network access the container has.

🖼
Node Image (Template)

Combines one Runtime + one Network recipe into a buildable Docker image.

Creating a Runtime Recipe

All of this happens in the dashboard at /templates.

1
Foundry → Runtime Image Recipes → Create

The 5-step wizard opens.

2
Identity

Name your recipe (e.g. "Data Science Runtime").

3
Base Image

Choose the base OS. This determines the OS family (DEBIAN/ALPINE) which affects available packages. Only Smelter-approved base images appear if enforcement is STRICT.

4
Packages

Add packages from the Smelter-approved list. Autocomplete shows only approved ingredients for your OS family and ecosystem.

5
Tools & Review

Add optional tools from the Capability Matrix, review the recipe, and create.

Building the Node Image

1
Foundry → Node Images → Create Template

Select your Runtime and Network recipes. Name the template.

2
Click Build

Foundry generates a Dockerfile, injects all mirror configs (pip.conf, APT proxy, apk repos, .condarc, npm registry), builds the image, and pushes to your local registry.

3
Post-build validation

An ephemeral container verifies the image starts correctly. On success: status → ACTIVE, Bill of Materials captured.

Image lifecycle

Change a template's status at any time: ACTIVE (nodes can use it), DEPRECATED (warning), or REVOKED (blocked at enrollment and work-pull).

Bill of Materials

After a successful build, click BOM on any template to see every installed package — pip, apt, apk, npm, conda. The BOM Explorer in Admin lets you search across all built images: "which nodes have numpy 1.26?"

08

Validation & Troubleshooting

Verify your mirrors are working, validate your images, and fix common problems.

Dashboard health checks

The Smelter Registry tab shows a Repository Health card with live metrics for all configured mirrors:

PyPI Green/red badge — devpi reachable?
APT Green/red badge — apt-cacher-ng reachable?
Disk Progress bar — cache disk usage (GB used / total)

Common problems

!
"No matching distribution found" during build

Wrong devpi URL. Verify path is /root/pypi/+simple/. Also check: is the package approved and MIRRORED in the Smelter?

!
Ingredient stuck at PENDING

Expand the row and check the mirror log. Common causes: no internet, mirror container not running, package name typo. If air-gapped, use the upload button.

!
APT/apk packages not caching

Check the mirror container is on the same Docker network. Verify with docker logs. APT: look for 200 (ca) entries. apk/nginx: check access.log for HIT/MISS.

!
Build rejected in STRICT mode

The blueprint contains unapproved packages. The error message lists which ones. Approve them in the Smelter and wait for MIRRORED status.

!
Docker Hub rate limit hit

Ensure registry-cache is running and Foundry builds pull through it. Check docker logs registry-cache for cache hit/miss rates.

!
Conda "PackageNotFoundError"

The local mirror doesn't have the package yet. Approve it in the Smelter with ecosystem Conda and wait for MIRRORED. Or check the channel name — conda-forge vs defaults.

End-to-end checklist

Mirror containers running

devpi (:3141), apt-cacher-ng (:3142), apk-cache (:3143), registry-cache (:5000), and optionally Verdaccio (:4873), conda-mirror, BaGet (:5555).

Mirror URLs configured

All mirror URLs set in Admin → Smelter Registry. Repository Health shows green badges.

Packages approved and mirrored

All packages your blueprints need are in the Smelter with MIRRORED status across all ecosystems.

Enforcement mode set

STRICT for production, WARNING for development.

Foundry builds succeed

Builds complete with Smelt-Check passing and BOM captured. Templates are ACTIVE.


🎓
Course complete

You now know how to set up all seven package mirrors, approve packages through the Smelter, build custom node images in the Foundry, and troubleshoot common issues. For detailed reference, see the Package Mirrors Runbook and the Foundry Feature Guide.