Skip to content

Daemonless pkg cache Daemonless pkg cache

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
Daemonless pkg cache Host Port
Daemonless pkg cache /config Path
Daemonless pkg cache /cache Path

Build Status Last Commit

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
Daemonless daemonless/pkg-cache
Source daemonless/pkg-cache
Website daemonless.io/images/pkg-cache

Version Tags

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.

Root Privileges Required

Podman on FreeBSD currently requires root. All commands must be run as root (or via doas/sudo).

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.

Deployment

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.
      - 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.
    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
1
2
3
4
5
6
7
8
# .env

DIRECTOR_PROJECT=pkg-cache
TZ=UTC
PKG_UPSTREAM=pkg.FreeBSD.org
PKG_CACHE_SIZE=50g
ENABLE_STATS=false
SKIP_CHOWN=true
# 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}'
        - SKIP_CHOWN: !ENV '${SKIP_CHOWN}'
    volumes:
      - PKG_CACHE_CONFIG_PATH: /config
      - PKG_CACHE_CACHE_PATH: /cache
      - /etc/resolv.conf: /etc/resolv.conf
volumes:
  PKG_CACHE_CONFIG_PATH:
    device: '/path/to/containers/pkg-cache'
  PKG_CACHE_CACHE_PATH:
    device: '/path/to/containers/pkg-cache/cache'
  /etc/resolv.conf:
    device: '/etc/resolv.conf'
1
2
3
4
5
6
# Makejail

ARG tag=latest

OPTION overwrite=force
OPTION from=ghcr.io/daemonless/pkg-cache:${tag}
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 \
  -e SKIP_CHOWN=true \
  -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:latest
appjail 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 \
  -e SKIP_CHOWN=true \
  -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-cache
- 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"
      SKIP_CHOWN: "true"
    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

Interactive Configuration

Parameters

Environment Variables

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.
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.

Volumes

Path Description
/config Logs, generated stats storage (log/, 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; 10G is fine for light use).
/etc/resolv.conf Host DNS resolver config. Optional but recommended so upstream pkg resolution matches the host.

Ports

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

How it works

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.

Pointing clients at the cache

On a build host (or any FreeBSD box), drop in /usr/local/etc/pkg/repos/FreeBSD.conf:

1
2
3
4
5
6
7
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.conf

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).

Implementation Details

  • Architectures: amd64
  • User: bsd (UID/GID set via PUID/PGID). Defaults to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.1).

Need help? Join our Discord community.