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.

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

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
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
- 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"
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}
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

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, aarch64
  • 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.