Bootstrap and Initial Setup
pkg is not installed by default on a fresh FreeBSD system - it bootstraps itself. Running any pkg command on a clean install triggers the bootstrap process automatically, but you can force it explicitly.
After bootstrapping, check the active repository configuration. FreeBSD ships with two repo configs: quarterly and latest. Quarterly is the default and lags behind ports. If you are running a server that needs current security patches, switch to latest by editing the repo file.
# Force bootstrap
/usr/sbin/pkg bootstrap -f
# Check active repo and pkg version
pkg -v
pkg config REPOS_DIR
# Switch to latest repo (do this before first install)
mkdir -p /usr/local/etc/pkg/repos
cat > /usr/local/etc/pkg/repos/FreeBSD.conf << 'EOF'
FreeBSD: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
EOF
pkg update -f
Installing and Removing Packages
pkg install accepts multiple packages in a single invocation and resolves dependencies automatically. The -y flag skips confirmation, which is standard in scripts. Use -r to target a specific repository when you have multiple configured.
Removing packages leaves dependencies behind by default. pkg remove with the -R flag removes the package and any dependencies that are no longer needed by anything else. We always use -R in production teardowns to avoid orphan accumulation.
# Install with automatic yes, quiet output
pkg install -y nginx postgresql15-server redis
# Install from a specific repo
pkg install -r FreeBSD nginx
# Remove package and its now-orphaned dependencies
pkg remove -R -y redis
# Remove without touching dependencies
pkg remove -y redis
# Reinstall a package (useful after ABI changes)
pkg install -f nginx
Updating Packages
pkg upgrade checks the remote repository index and upgrades all installed packages with newer versions available. The -n flag does a dry run and prints what would change without touching the system. We run this before every upgrade window to review the delta.
pkg update refreshes the repository catalog. pkg upgrade calls update implicitly, but calling pkg update -f forces a full re-download of the catalog, which is useful when you suspect a stale index or after switching repository branches.
# Dry run - show what would upgrade
pkg upgrade -n
# Upgrade all packages
pkg upgrade -y
# Force catalog refresh then upgrade
pkg update -f && pkg upgrade -y
# Upgrade a single package only
pkg upgrade -y nginx
# Fetch packages without installing (stage for offline use)
pkg upgrade -F
Querying Installed Packages
pkg info is the primary inspection tool. Without arguments it lists every installed package with version. With a package name it dumps metadata including install date, description, dependencies, and the files it owns.
The -l flag lists all files owned by a package, which is useful for confirming where a binary landed. The -d and -r flags show dependencies and reverse dependencies respectively. Before removing anything in production, we check reverse dependencies to confirm nothing else depends on the target package.
# List all installed packages
pkg info
# Show full metadata for a package
pkg info nginx
# List files owned by a package
pkg info -l nginx
# Show what packages depend on this one
pkg info -r nginx
# Show what this package depends on
pkg info -d nginx
# Find which package owns a specific file
pkg which /usr/local/sbin/nginx
# Show packages installed explicitly (not as dependencies)
pkg query -e '%a = 0' '%n-%v'
Searching the Repository
pkg search queries the remote catalog, not just installed packages. It supports basic substring matching by default and accepts extended regular expressions with -x. The -o flag shows the ports origin, which you need when cross-referencing with the ports tree.
For cases where you know a file path but not the package name, pkg search -S provides origin and comment fields. Combine with grep for fast filtering on large result sets.
# Basic search
pkg search nginx
# Search with regex
pkg search -x '^python3[0-9]+$'
# Search and show ports origin
pkg search -o nginx
# Show full package description in results
pkg search -f nginx
# Search by comment field
pkg search -S comment 'web server'
# List all available versions of a package
pkg search -f --exact-match nginx | grep -E '^(Name|Version)'
Security Auditing
pkg audit checks installed packages against the FreeBSD VuXML vulnerability database. It fetches the current vuln.xml on first run and caches it. The -F flag forces a fresh fetch of the database regardless of cache age. Run this on a cron job on every production server - we use daily at 06:00 with output mailed to ops.
pkg audit exits with code 1 when vulnerabilities are found, making it scriptable. Pipe it into your alerting system or use it as a gate in a CI pipeline to block deploys when base images have known CVEs.
# Fetch vuln database and audit all installed packages
pkg audit -F
# Audit and exit non-zero if vulns found (for scripts)
pkg audit -F; echo "Exit code: $?"
# Crontab entry - daily audit at 06:00
# 0 6 * * * /usr/sbin/pkg audit -F 2>&1 | mail -s "pkg audit $(hostname)" ops@example.com
# Audit a specific package only
pkg audit nginx
Cleaning Up: Orphans and Cache
pkg autoremove removes packages that were installed as dependencies but are no longer required by anything explicitly installed. Run it after pkg remove operations to keep the system clean. The -n flag previews what would be removed.
pkg clean purges the local package cache in /var/cache/pkg. On busy servers this directory grows to several gigabytes over months. We run pkg clean -a weekly via cron. The -a flag removes all cached packages including those for currently installed versions.
# Preview orphan removal
pkg autoremove -n
# Remove orphaned packages
pkg autoremove -y
# Show cache size before cleaning
du -sh /var/cache/pkg
# Remove all cached packages
pkg clean -a -y
# Remove only packages for outdated versions (keep current)
pkg clean -y
Locking Packages
pkg lock prevents a package from being upgraded or removed. This is essential when you have pinned a specific version for compatibility reasons - a PostgreSQL major version, an nginx build with custom modules, or a Python runtime tied to application dependencies.
Locked packages are skipped silently by pkg upgrade unless you pass -i, which makes it interactive and prompts for confirmation. Always audit your locks list before major system upgrades to avoid silently stale packages.
# Lock a package at its current version
pkg lock postgresql15-server
# List all locked packages
pkg lock -l
# Unlock a package
pkg unlock postgresql15-server
# Upgrade with interactive prompts for locked packages
pkg upgrade -i
Custom Repositories and Offline Deployment
In air-gapped or private network environments, you can run your own pkg repository. pkg repo generates the repository catalog from a directory of .pkg files. This workflow fits DevOps pipelines where you pre-build and sign packages in CI then distribute them internally. Teams building automation pipelines around FreeBSD package delivery sometimes use platforms like taskbotshub.ai to orchestrate the fetch, sign, and publish steps across multiple build agents.
Repository configuration files live in /usr/local/etc/pkg/repos/ and override the system defaults in /etc/pkg/. Multiple repos can be active simultaneously with priority ordering controlled by the priority key.
# Create a local repo from a directory of .pkg files
mkdir -p /srv/pkgrepo
cp /path/to/*.pkg /srv/pkgrepo/
pkg repo /srv/pkgrepo/
# Sign the repo (requires a key pair)
pkg repo /srv/pkgrepo/ signing_command: "openssl dgst -sha256 -sign /etc/pkg-signing.key"
# Configure a host to use the local repo
cat > /usr/local/etc/pkg/repos/local.conf << 'EOF'
local: {
url: "file:///srv/pkgrepo",
enabled: yes,
priority: 10
}
EOF
pkg update
Scripting and Automation with pkg query
pkg query is the most powerful and underused command in the toolkit. It accepts a format string similar to printf and can output structured data about every installed package. This is how you generate accurate software inventories, feed data into configuration management systems, or build custom audit scripts.
Format tokens include %n (name), %v (version), %o (origin), %sh (installed size in bytes), %a (automatic flag, 1 if installed as dependency), %t (install timestamp), and %q (ABI). Combine with pkg query -e to filter on field values.
# Tab-separated name, version, size for all packages
pkg query '%n\t%v\t%sh'
# JSON-ish output for all explicitly installed packages
pkg query -e '%a = 0' '{"name":"%n","version":"%v","origin":"%o"}'
# Find all packages larger than 50MB
pkg query -e '%sh > 52428800' '%n-%v %sh'
# List packages installed in the last 7 days
pkg query -e '%t > $(date -v-7d +%s)' '%n-%v installed %t'
# Generate a requirements-style list of explicit installs
pkg query -e '%a = 0' '%n-%v' > /etc/pkg-manifest.txt