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. | Most users — recommended. |
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. Keep the /cache volume at least this big.
- SKIP_CHOWN=true # Skip the startup recursive chown of /config and /cache once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts.
- PKG_LOG_MAX_SIZE=50m # Size cap for each on-disk log in /config/log (access.log, error.log), e.g. 10m, 50m, 1g. A log past this is rotated away so /config can't fill; the stats totals are kept separately and survive rotation.
volumes:
- "/containers/pkg-cache:/config"
- "/containers/pkg-cache/cache:/cache"
ports:
- "80:80"
# always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
restart: alwaysSave as compose.yaml, then run podman-compose up -d.
.env:
# .env
DIRECTOR_PROJECT=pkg-cache
TZ=UTC
PKG_UPSTREAM=pkg.FreeBSD.org
PKG_CACHE_SIZE=50g
SKIP_CHOWN=true
PKG_LOG_MAX_SIZE=50m
appjail-director.yml:
# appjail-director.yml
options:
- virtualnet: ':<random> default'
- nat:
services:
pkg-cache:
name: pkg_cache
options:
- container: 'args:--pull'
- expose: '80:80 proto:tcp'
oci:
user: root
environment:
- TZ: !ENV '${TZ}'
- PKG_UPSTREAM: !ENV '${PKG_UPSTREAM}'
- PKG_CACHE_SIZE: !ENV '${PKG_CACHE_SIZE}'
- SKIP_CHOWN: !ENV '${SKIP_CHOWN}'
- PKG_LOG_MAX_SIZE: !ENV '${PKG_LOG_MAX_SIZE}'
volumes:
- pkg-cache: /config
- pkg-cache_cache: /cache
volumes:
pkg-cache:
device: '/containers/pkg-cache'
pkg-cache_cache:
device: '/containers/pkg-cache/cache'Makejail:
# Makejail
ARG tag=latest
OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/pkg-cache:${tag}
Save the files above, then run appjail-director up.
Warning
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 jail's IPv4 address or hostname assigned by the virtual network.
To avoid exposing ports, just remove the expose option in your appjail-director.yml or from your command-line arguments.
Warning
Bastille's OCI support is experimental. It requires buildah and shares the host network stack (inherit). Mount volumes with --volume HOST JAIL; without it, image-declared volumes are stored under ${bastille_volumesdir}/${jail}.
services:
pkg-cache:
name: pkg-cache
image: "ghcr.io/daemonless/pkg-cache:latest"
network:
- mode: host
environment:
- TZ=UTC
- PKG_UPSTREAM=pkg.FreeBSD.org
- PKG_CACHE_SIZE=50g
- SKIP_CHOWN=true
- PKG_LOG_MAX_SIZE=50m
volumes:
- "/containers/pkg-cache:/config"
- "/containers/pkg-cache/cache:/cache"Save as bastille-compose.yml, then run bastille up.
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. Keep the /cache volume at least this big. |
SKIP_CHOWN |
true |
Skip the startup recursive chown of /config and /cache once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts. |
PKG_LOG_MAX_SIZE |
50m |
Size cap for each on-disk log in /config/log (access.log, error.log), e.g. 10m, 50m, 1g. A log past this is rotated away so /config can't fill; the stats totals are kept separately and survive rotation. |
| Path | Description |
|---|---|
/config |
Logs (log/), stats totals (stats/) and the startup ownership marker (.chown_done). Mount as a persistent volume so the chown is skipped on later starts. |
/cache |
Package cache storage (proxy_cache). Size to match max_size in nginx.conf (default 50G). |
| Port | Protocol | Description |
|---|---|---|
80 |
TCP | HTTP — pkg clients point their FreeBSD.conf url here; browsers get the stats page |
Package files (*.pkg) are cached for 365 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: "http://<cache-host>/${ABI}/quarterly",
mirror_type: "none",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}Use plain http://, not pkg+http:// — pkg on FreeBSD 15 rejects the
pkg+ scheme unless mirror_type is srv.
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.
pkg audit can use the cache too — the appliance proxies the FreeBSD
vulnerability database at /vuxml/:
echo 'VULNXML_SITE = "http://<cache-host>/vuxml/vuln.xml.xz";' >> /usr/local/etc/pkg.confNo 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 — a full set of packages for one FreeBSD release and arch is around 10G).
Open http://<cache-host>/ in a browser. The landing page shows the hit
rate, bytes served from cache versus fetched upstream, cache disk usage,
top clients and the most recent requests, refreshed every few seconds.
Nothing to enable; the same numbers are at /stats.json.
Architectures: amd64, aarch64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD 15.1
Need help? Join our Discord community.