Managed FreeBSD pkg caching appliance — proxies pkg.FreeBSD.org to speed up package fetches across image builds and insulate them from upstream rate limits/outages.
| Port | 80 |
| Registry | ghcr.io/daemonless/pkg-cache |
| Source | https://github.com/daemonless/pkg-cache |
| Website | https://daemonless.io/images/pkg-cache/ |
| Tag | Description | Best For |
|---|---|---|
latest |
daemonless Appliance. Managed FreeBSD pkg cache proxy built on nginx-base. | Shared package cache for daemonless builds and FreeBSD hosts. |
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
services:
pkg-cache:
image: "ghcr.io/daemonless/pkg-cache:latest"
container_name: pkg-cache
environment:
- TZ=UTC # Timezone for the container
- PKG_UPSTREAM=pkg.FreeBSD.org # FreeBSD pkg mirror to proxy, e.g. pkg1.us.freebsd.org or pkg0.eu.freebsd.org. Defaults to the primary pkg.FreeBSD.org.
- PKG_CACHE_SIZE=50g # Max on-disk cache size (nginx max_size), e.g. 10g, 100g, 500g. 10g is plenty for light use; keep the /cache volume at least this big.
- ENABLE_STATS=false # Set to true to enable the GoAccess real-time stats dashboard on port 7890.
volumes:
- "/path/to/containers/pkg-cache:/config"
- "/path/to/containers/pkg-cache/cache:/cache"
- "/etc/resolv.conf:/etc/resolv.conf:ro"
ports:
- "80:80"
- "7890:7890"
restart: unless-stopped.env:
# .env
DIRECTOR_PROJECT=pkg-cache
TZ=UTC
PKG_UPSTREAM=pkg.FreeBSD.org
PKG_CACHE_SIZE=50g
ENABLE_STATS=false
appjail-director.yml:
# appjail-director.yml
options:
- virtualnet: ':<random> default'
- nat:
services:
pkg-cache:
name: pkg_cache
options:
- container: 'boot args:--pull'
- expose: '80:80 proto:tcp' \
- expose: '7890:7890 proto:tcp' \
oci:
user: root
environment:
- TZ: !ENV '${TZ}'
- PKG_UPSTREAM: !ENV '${PKG_UPSTREAM}'
- PKG_CACHE_SIZE: !ENV '${PKG_CACHE_SIZE}'
- ENABLE_STATS: !ENV '${ENABLE_STATS}'
volumes:
- pkg-cache: /config
- pkg-cache_cache: /cache
- etc_resolv_conf: /etc/resolv.conf
volumes:
pkg-cache:
device: '/path/to/containers/pkg-cache'
pkg-cache_cache:
device: '/path/to/containers/pkg-cache/cache'
etc_resolv_conf:
device: '/etc/resolv.conf'Makejail:
# Makejail
ARG tag=latest
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/pkg-cache:${tag}
Note: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.
podman run -d --name pkg-cache \
-p 80:80 \
-p 7890:7890 \
-e TZ=UTC \
-e PKG_UPSTREAM=pkg.FreeBSD.org \
-e PKG_CACHE_SIZE=50g \
-e ENABLE_STATS=false \
-v /path/to/containers/pkg-cache:/config \
-v /path/to/containers/pkg-cache/cache:/cache \
-v /etc/resolv.conf:/etc/resolv.conf:ro \
ghcr.io/daemonless/pkg-cache:latestappjail oci run -Pd \
-o overwrite=force \
-o container="args:--pull" \
-o virtualnet=":<random> default" \
-o nat \
-o expose="80:80 proto:tcp" \
-o expose="7890:7890 proto:tcp" \
-e TZ=UTC \
-e PKG_UPSTREAM=pkg.FreeBSD.org \
-e PKG_CACHE_SIZE=50g \
-e ENABLE_STATS=false \
-o fstab="/path/to/containers/pkg-cache /config <pseudofs>" \
-o fstab="/path/to/containers/pkg-cache/cache /cache <pseudofs>" \
-o fstab="/etc/resolv.conf /etc/resolv.conf <pseudofs>" \
ghcr.io/daemonless/pkg-cache:latest pkg-cacheNote: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.
- name: Deploy pkg-cache
containers.podman.podman_container:
name: pkg-cache
image: "ghcr.io/daemonless/pkg-cache:latest"
state: started
restart_policy: always
env:
TZ: "UTC"
PKG_UPSTREAM: "pkg.FreeBSD.org"
PKG_CACHE_SIZE: "50g"
ENABLE_STATS: "false"
ports:
- "80:80"
- "7890:7890"
volumes:
- "/path/to/containers/pkg-cache:/config"
- "/path/to/containers/pkg-cache/cache:/cache"
- "/etc/resolv.conf:/etc/resolv.conf:ro"Access at: http://localhost:80
| Variable | Default | Description |
|---|---|---|
TZ |
UTC |
Timezone for the container |
PKG_UPSTREAM |
pkg.FreeBSD.org |
FreeBSD pkg mirror to proxy, e.g. pkg1.us.freebsd.org or pkg0.eu.freebsd.org. Defaults to the primary pkg.FreeBSD.org. |
PKG_CACHE_SIZE |
50g |
Max on-disk cache size (nginx max_size), e.g. 10g, 100g, 500g. 10g is plenty for light use; keep the /cache volume at least this big. |
ENABLE_STATS |
false |
Set to true to enable the GoAccess real-time stats dashboard on port 7890. |
| Path | Description |
|---|---|
/config |
Logs and generated stats storage (log/, stats/). The nginx config is appliance-managed at startup. |
/cache |
Package cache storage (proxy_cache). Size to match max_size in nginx.conf (default 50G; 10G is fine for light use). |
/etc/resolv.conf |
Host DNS resolver config. Optional but recommended so upstream pkg resolution matches the host. |
| Port | Protocol | Description |
|---|---|---|
80 |
TCP | HTTP — pkg clients point their FreeBSD.conf url here |
7890 |
TCP | GoAccess stats dashboard (HTML) — enabled via ENABLE_STATS=true |
Package files (*.pkg) are cached for 30 days — their filenames are
versioned and immutable, so a HIT is always safe. Catalog metadata
(meta.conf, packagesite.*, data.*) is cached for 1 minute instead,
so pkg keeps resolving the latest updates without serving stale
catalogs.
On a build host (or any FreeBSD box), drop in
/usr/local/etc/pkg/repos/FreeBSD.conf:
FreeBSD: {
url: "pkg+http://<cache-host>/${ABI}/quarterly",
mirror_type: "none",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}Then pkg update fetches through the cache — first pull is a MISS,
everything after is a HIT, and package signatures still verify end-to-end.
No nginx config is required; the appliance renders its managed config at startup from environment variables. Size the /cache volume to at least PKG_CACHE_SIZE (default 50G — 10G is plenty for a handful of hosts/images, bump it up if you're caching a large fleet).
Inspired by this video.
Architectures: amd64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD latest
Need help? Join our Discord community.