🧀 The Ports Tree: Building FreeBSD Software from Source

Mittens or gloves? Pre-built packages fit most hands. The ports tree lets you cut software to fit yours: your options, your modules, your CPU

Video: The Ports Tree (FreeBSD video)
Prefer to watch? This short video runs this page's main commands on a real FreeBSD 15.0 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.

Mittens and gloves

Here is the easiest way to remember the choice FreeBSD gives you:

Both give you the same program in the end, installed in the same place and tracked by the same pkg database. The difference is who compiled it, and with which settings. Everything on this page was run for real on FreeBSD 15.0-RELEASE, which has the compiler (clang 19) built in. The upgrade page shows the mitten side.

What the ports tree is

The ports tree lives in /usr/ports. It is not software. It is a collection of recipes: one small folder per program, saying where to download the source, which patches to apply, which options exist, what the program needs and how to compile and install it. Our copy came off the installation disc, and unpacking it into the ZFS dataset that FreeBSD had already prepared for it took half a minute:

root@practice:~ # time tar -xf /mnt/usr/freebsd-dist/ports.txz -C /
       29.53 real         8.26 user        19.59 sys
root@practice:~ # ls /usr/ports | head -12
.arcconfig
.gitignore
.hooks
.mailmap
CHANGES
CONTRIBUTING.md
COPYRIGHT
GIDs
Keywords
MOVED
Makefile
Mk
root@practice:~ # ls -d /usr/ports/*/ | wc -l
      66
root@practice:~ # ls /usr/ports/sysutils | wc -l
    1643
root@practice:~ # du -sh /usr/ports
932M	/usr/ports
root@practice:~ # zfs list zroot/usr/ports
NAME              USED  AVAIL  REFER  MOUNTPOINT
zroot/usr/ports   902M  11.3G   902M  /usr/ports

time shows 30 seconds, and ports.txz was a 52 MB archive (from its date on the disc, a snapshot of the tree from the end of November 2025). The tree has 66 category folders, such as sysutils, www and devel, and sysutils alone holds 1643 ports. Unpacked it is 932 MB. On a normal machine you fetch and update the tree with git (git clone to get it, and git pull to update: from the documentation, not run here), and the installer's snapshot is just the quick start.

Anatomy of a port

root@practice:~ # (cd /usr/ports/sysutils/tree && ls)
Makefile	distinfo	pkg-descr
root@practice:~ # (cd /usr/ports/sysutils/tree && cat Makefile)
PORTNAME=	tree
DISTVERSION=	2.2.1
CATEGORIES=	sysutils
MASTER_SITES=	https://oldmanprogrammer.net/tar/tree/ \
		https://fossies.org/linux/misc/

MAINTAINER=	gb@unistra.fr
COMMENT=	Display a tree-view of directories with optional color or HTML output
WWW=		https://oldmanprogrammer.net/source.php?dir=projects/tree

LICENSE=	GPLv2+
LICENSE_FILE=	${WRKSRC}/LICENSE

USES=		gmake tar:tgz
MAKE_ARGS=	CC="${CC}" CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}"

PLIST_FILES=	bin/tree \
		share/man/man1/tree.1.gz
PORTDOCS=	CHANGES README TODO

OPTIONS_DEFINE=	DOCS

do-install:
	${INSTALL_PROGRAM} ${WRKSRC}/tree ${STAGEDIR}${PREFIX}/bin
	${INSTALL_MAN} ${WRKSRC}/doc/*.1 ${STAGEDIR}${PREFIX}/share/man/man1

do-install-DOCS-on:
	@${MKDIR} ${STAGEDIR}${DOCSDIR}
	${INSTALL_DATA} ${PORTDOCS:S,^,${WRKSRC}/,} ${STAGEDIR}${DOCSDIR}

.include <bsd.port.mk>
root@practice:~ # (cd /usr/ports/sysutils/tree && make -V PORTVERSION -V PKGNAME -V MASTER_SITES)
2.2.1
tree-2.2.1
https://oldmanprogrammer.net/tar/tree/  https://fossies.org/linux/misc/
root@practice:~ # (cd /usr/ports/sysutils/tree && make pretty-print-build-depends-list)
pretty-print-build-depends-list requires an INDEX file (INDEX-15). Please run make index or make fetchindex.

A port is a few files. tree is a very small one, and its Makefile is the whole recipe, in a dozen lines:

make -V NAME asks the recipe for any of its variables (make -V PORTVERSION gave 2.2.1). The last command printed an error that teaches something: make pretty-print-build-depends-list needs an INDEX file, which you get with make fetchindex (from the documentation, not run here). Use make build-depends-list instead, as you will see next.

The options dialog

Many ports have options. Run make config in the port's folder and you get a small full-screen dialog:

             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€tree-2.2.1β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚ 'F1' for Ports Collection help.                   β”‚
             β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
             β”‚ β”‚new [X] DOCS Build and/or install documentationβ”‚ β”‚
             β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
             β”‚               [  OK  ]     [Cancel]               β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This is the options dialog for tree: one option, DOCS (build and install the documentation), shown as [X] meaning on. Move with the arrow keys, toggle with the space bar, press Enter on OK. (The word new in front shows that the option had never been set before.) Your choice is saved to a file:

root@practice:/usr/ports/sysutils/tree # ls /var/db/ports/sysutils_tree && cat /var/db/ports/sysutils_tree/options
options
# This file is auto-generated by 'make config'.
# Options for tree-2.2.1
_OPTIONS_READ=tree-2.2.1
_FILE_COMPLETE_OPTIONS_LIST=DOCS
OPTIONS_FILE_SET+=DOCS

Next time the port is built, no questions are asked. The file lives in /var/db/ports/ under the port's name.

The classic gotcha: if the options have not been saved yet, make install opens this dialog by itself, and sits there waiting for a human, for every port that needs it, including the dependencies. Our first attempt at building tree seemed to hang for ten minutes: it was waiting at this dialog, and after we answered it, again for the dependency gmake. There are two cures: make config-recursive asks every question for the port and its dependencies up front, and BATCH=yes says "do not ask, accept the defaults", which is what scripts use.

Your first build

root@practice:~ # (cd /usr/ports/sysutils/tree && make build-depends-list)
/usr/ports/ports-mgmt/pkg
/usr/ports/devel/gmake
root@practice:~ # (cd /usr/ports/sysutils/tree && make showconfig)
===> The following configuration options are available for tree-2.2.1:
     DOCS=on: Build and/or install documentation
===> Use 'make config' to modify these settings
root@practice:~ # (cd /usr/ports/sysutils/tree && env ASSUME_ALWAYS_YES=yes make install-missing-packages)
Updating FreeBSD-ports repository catalogue...
[... package catalogue lines left out ...]
New packages to be INSTALLED:
	gettext-runtime: 1.0_1 [FreeBSD-ports]
	gmake: 4.4.1 [FreeBSD-ports]
	indexinfo: 0.3.1_1 [FreeBSD-ports]
Number of packages to be installed: 3
The process will require 4 MiB more space.
789 KiB to be downloaded.
Checking integrity... done (0 conflicting)
[1/3] Installing indexinfo-0.3.1_1...
[2/3] Installing gettext-runtime-1.0_1...
[3/3] Installing gmake-4.4.1...

make build-depends-list shows what tree needs to be built: pkg and gmake. Then a very useful trick: make install-missing-packages installs the missing dependencies as ready-made packages (mittens) rather than compiling them (gloves). That is how people mix the two styles: compile the program you care about, and take the supporting parts as packages. (We set ASSUME_ALWAYS_YES=yes so that pkg does not stop to ask.) Now the build:

root@practice:~ # (cd /usr/ports/sysutils/tree && time make install clean BATCH=yes)
===>   tree-2.2.1 depends on package: gmake>=4.4.1 - found
===>  Configuring for tree-2.2.1
===>  Building for tree-2.2.1
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o tree.o tr ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o list.o li ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o hash.o ha ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o color.o c ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o file.o fi ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o filter.o  ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o info.o in ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o unix.o un ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o xml.o xml ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o json.o js ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o html.o ht ...
cc -O2 -pipe  -fstack-protector-strong -fno-strict-aliasing  -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -c -o strverscm ...
cc  -o tree tree.o list.o hash.o color.o file.o filter.o info.o unix.o xml.o json.o html.o strverscmp.o
===>  Staging for tree-2.2.1
===>   Generating temporary packing list
install  -s -m 555 /usr/ports/sysutils/tree/work/tree-2.2.1/tree /usr/ports/sysutils/tree/work/stage/usr/local/bin
install  -m 444 /usr/ports/sysutils/tree/work/tree-2.2.1/doc/*.1 /usr/ports/sysutils/tree/work/stage/usr/local/share/m ...
install  -m 0644 /usr/ports/sysutils/tree/work/tree-2.2.1/CHANGES /usr/ports/sysutils/tree/work/tree-2.2.1/README /usr ...
====> Compressing man pages (compress-man)
===>  Installing for tree-2.2.1
===>  Checking if tree is already installed
===>   Registering installation for tree-2.2.1
Installing tree-2.2.1...
===>  Cleaning for tree-2.2.1
        6.36 real         6.99 user         1.15 sys

(Long lines are shortened.) The whole life of a port is in this output: it checks the dependencies, fetches the source, checks the checksum, configures, builds (the cc lines are the compiler at work, with the standard flags -O2 -pipe -fstack-protector-strong), stages the files into a temporary folder, installs them, registers the package and cleans up the work folder. For this tiny program it took 6.4 seconds. And afterwards:

root@practice:~ # which tree
/usr/local/bin/tree
root@practice:~ # tree --version
tree v2.2.1 Β© 1996 - 2024 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro
root@practice:~ # pkg info tree
tree-2.2.1
Name           : tree
Version        : 2.2.1
Installed on   : Tue Oct  6 00:58:36 2026 UTC
Origin         : sysutils/tree
Architecture   : FreeBSD:15:amd64
Prefix         : /usr/local
Categories     : sysutils
Licenses       : GPLv2+
Maintainer     : gb@unistra.fr
WWW            : https://oldmanprogrammer.net/source.php?dir=projects/tree
Comment        : Display a tree-view of directories with optional color or HTML output
Options        :
	DOCS           : on
Shared Libs required:
	libc.so.7
Annotations    :
	FreeBSD_version: 1500068
Flat size      : 134KiB
Description    :
Tree is a simple utility that displays a tree-view of directories
and files. It has options to use ANSI color escape-sequences or ASCII
graphic characters, and can also output in HTML format.
root@practice:~ # pkg which /usr/local/bin/tree
/usr/local/bin/tree was installed by package tree-2.2.1
root@practice:~ # ls /usr/ports/sysutils/tree
Makefile	distinfo	pkg-descr

The result is an ordinary package. pkg info tree lists it with its Origin, sysutils/tree, and the options it was built with: DOCS: on. pkg which agrees that the file belongs to it. Nothing tells you whether it was compiled by you or downloaded: that is the point.

Gloves: building nginx to fit

Now a real fitting. www/nginx has about 90 options, including dozens of modules. The package from the repository has most of the common ones on. We want a lean web server: no WebDAV, FLV or MP4 streaming, no mail proxy, no HTTP/3, no sample pages, and the little degradation module switched on, with the compiler told to optimise for this machine's processor. All of that goes in /etc/make.conf, the file the build reads for your own settings:

root@practice:~ # echo 'CPUTYPE?=native' >> /etc/make.conf
root@practice:~ # echo 'www_nginx_UNSET=MAIL MAIL_SSL HTTP_DAV HTTP_FLV HTTP_MP4 HTTP_RANDOM_INDEX HTTPV3 WWW' >> /etc/make.conf
root@practice:~ # echo 'www_nginx_SET=HTTP_DEGRADATION' >> /etc/make.conf
root@practice:~ # cat /etc/make.conf
CPUTYPE?=native
www_nginx_UNSET=MAIL MAIL_SSL HTTP_DAV HTTP_FLV HTTP_MP4 HTTP_RANDOM_INDEX HTTPV3 WWW
www_nginx_SET=HTTP_DEGRADATION
root@practice:~ # (cd /usr/ports/www/nginx && make showconfig | grep -E 'HTTP_DAV|HTTP_FLV|HTTP_MP4|MAIL=|HTTPV3=|HTTP_DEGRADATION|WWW=|HTTP_REALIP')
     WWW=off: Enable html sample files
     HTTP_DAV=off: Enable http_webdav module
     HTTP_DEGRADATION=on: Enable http_degradation module
     HTTP_FLV=off: Enable http_flv module
     HTTP_MP4=off: Enable http_mp4 module
     HTTP_REALIP=on: Enable http_realip module
     HTTPV3=off: Enable HTTP/3 protocol support
     MAIL=off: Enable IMAP4/POP3/SMTP proxy module
     HTTP_DAV_EXT=off: 3rd party webdav_ext module

A lesson in fitting the tree to the repository

When we started the build, it ran for more than twenty minutes, compiling perl5, texinfo and a chain of other tools, before we stopped it. Why?

root@practice:~ # (cd /usr/ports/devel/pcre2 && make -V PKGNAME)
pcre2-10.46
root@practice:~ # pkg rquery '%n-%v' pcre2
pcre2-10.49
root@practice:~ # (cd /usr/ports/www/nginx && make -V PKGNAME)
nginx-1.28.0_3,3
root@practice:~ # pkg rquery '%n-%v' nginx
nginx-1.30.6,3
root@practice:~ # (cd /usr/ports/devel/pcre2 && make -V BUILD_DEPENDS)
pkgconf>=1.3.0_1:devel/pkgconf autoconf>=2.72:devel/autoconf automake>=1.17:devel/automake libtoolize:devel/libtool

The ports tree and the package repository are two different snapshots in time. Our tree (from the end of November 2025) has pcre2-10.46, and the package repository today has pcre2-10.49. The nginx port wants pcre2, install-missing-packages looks for the version the tree asks for, and the repository did not have it. As far as we can tell, the build then set out to compile pcre2 itself. Its recipe (the last line above) needs autoconf, automake and libtool to build, and those pull in perl5 and more. The fix is to give it a version that satisfies the dependency: we installed pcre2 from the repository, and the build saw it was there.

root@practice:~ # (cd /usr/ports/www/nginx && time make install clean BATCH=yes)


===>  License BSD2CLAUSE accepted by the user
===>   nginx-1.28.0_3,3 depends on file: /usr/local/sbin/pkg - found
===> Fetching all distfiles required by nginx-1.28.0_3,3 for building
===>  Extracting for nginx-1.28.0_3,3
=> SHA256 Checksum OK for nginx-1.28.0.tar.gz.
===>  Patching for nginx-1.28.0_3,3
===>  Applying FreeBSD patches for nginx-1.28.0_3,3 from /usr/ports/www/nginx/files
===>   nginx-1.28.0_3,3 depends on shared library: libpcre2-8.so - found (/usr/local/lib/libpcre2-8.so)
===>  Configuring for nginx-1.28.0_3,3
[... about 130 lines of configure checks left out ...]
===>  Building for nginx-1.28.0_3,3
--- build ---
/usr/bin/make -f objs/Makefile
--- objs/src/core/nginx.o ---
--- objs/src/core/ngx_log.o ---
cc -c -O2 -pipe -march=native  -fstack-protector-strong -fno-strict-aliasing  -I /usr/local/include -I src/core  -I sr ...
[... the other compile lines are left out ...]
===>  Staging for nginx-1.28.0_3,3
===>   Generating temporary packing list
===> Creating groups
===> Staging rc.d startup script(s)
===>  Installing for nginx-1.28.0_3,3
===>  Checking if nginx is already installed
===>   Registering installation for nginx-1.28.0_3,3
Installing nginx-1.28.0_3,3...
===> Creating groups
===>  Cleaning for nginx-1.28.0_3,3
       93.44 real       139.84 user        16.34 sys

(The middle of the log is left out.) Now the build finds libpcre2-8.so, configures with the options we chose, and compiles with -march=native in every compile line: the glove being cut to fit. 93 seconds for the whole web server (about 140 seconds of processor time on 2 cores). The lesson: keep your ports tree and your package repository in step (update the tree with git, from the same branch the packages come from: from the documentation, not run here), or you will end up compiling things you did not mean to.

Mittens against gloves, side by side

On this machine, the host now runs nginx built from ports (gloves) and the jail from the jails page still runs the nginx package (mittens). That makes an honest comparison:

root@practice:~ # /usr/local/sbin/nginx -v
nginx version: nginx/1.28.0
root@practice:~ # jexec web /usr/local/sbin/nginx -v
nginx version: nginx/1.30.6
root@practice:~ # /usr/local/sbin/nginx -V 2>&1 | tr ' ' '\n' | grep -E 'dav|flv|mp4|degradation|v3|mail|march' 
--with-http_degradation_module
--without-mail_imap_module
--without-mail_pop3_module
--without-mail_smtp_module
root@practice:~ # jexec web /usr/local/sbin/nginx -V 2>&1 | tr ' ' '\n' | grep -E 'dav|flv|mp4|degradation|v3|mail|march' 
--with-http_v3_module
--with-http_dav_module
--with-http_flv_module
--with-http_mp4_module
--without-mail_imap_module
--without-mail_pop3_module
--without-mail_smtp_module
--with-mail_ssl_module
--with-mail=dynamic
root@practice:~ # ls -l /usr/local/sbin/nginx /jails/web/usr/local/sbin/nginx
-r-xr-xr-x  1 root wheel 1296512 Oct  1 05:54 /jails/web/usr/local/sbin/nginx
-r-xr-xr-x  1 root wheel 1055120 Oct  6 01:28 /usr/local/sbin/nginx

⚠️ Newer package repository (checked October 2026): the package repository now targets FreeBSD 15.1, while 15.0-RELEASE is end of life. On a 15.0 machine pkg may stop with Newer FreeBSD version for package ... [y/N], and the version numbers in the output below may differ from yours. To carry on, run export IGNORE_OSVERSION=yes first, or better, move to a supported release (see the upgrading guide).

root@practice:~ # pkg info nginx | grep -E 'Version|Origin|HTTP_DAV|HTTP_FLV|HTTP_MP4|MAIL |HTTPV3|HTTP_DEGRADATION|WWW '
Version        : 1.28.0_3,3
Origin         : www/nginx
WWW            : https://nginx.com/
Comment        : Robust and small WWW server
	HTTPV3         : off
	HTTPV3_BORING  : off
	HTTPV3_LSSL    : off
	HTTPV3_QTLS    : off
	HTTP_DAV       : off
	HTTP_DAV_EXT   : off
	HTTP_DEGRADATION: on
	HTTP_FLV       : off
	HTTP_MP4       : off
	MAIL           : off
	WWW            : off
root@practice:~ # pkg -j web info nginx | grep -E 'Version|Origin|HTTP_DAV|HTTP_FLV|HTTP_MP4|MAIL |HTTPV3|HTTP_DEGRADATION|WWW '
Version        : 1.30.6,3
Origin         : www/nginx
WWW            : https://nginx.com/
Comment        : Robust and small WWW server
	HTTPV3         : on
	HTTPV3_BORING  : off
	HTTPV3_LSSL    : off
	HTTP_DAV       : on
	HTTP_DAV_EXT   : off
	HTTP_DEGRADATION: off
	HTTP_FLV       : on
	HTTP_MP4       : on
	MAIL           : on
	WWW            : on
root@practice:~ # /usr/local/sbin/nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
root@practice:~ # whereis nginx
nginx: /usr/local/sbin/nginx /usr/local/share/man/man8/nginx.8.gz /usr/ports/www/nginx

pkg info records the build options of each: on the host, HTTP_DAV: off, HTTP_DEGRADATION: on, WWW: off; in the jail, the reverse. And the custom build passes its own syntax test (nginx -t). whereis nginx finds both the program and the port it came from: /usr/ports/www/nginx.

Tidying up

root@practice:~ # pkg autoremove -n
Updating database digests format:   0%Updating database digests format:   0%Updating database digests format: 100%
Checking integrity... done (0 conflicting)
Deinstallation has been requested for the following 15 packages:

Installed packages to be REMOVED:
	bsddialog: 1.1
	gettext-runtime: 1.0_1
	gettext-tools: 0.23.1_1
	gmake: 4.4.1
	help2man: 1.49.3_1
	indexinfo: 0.3.1_1
	libiconv: 1.17_1
	libtextstyle: 0.23.1
	p5-Locale-gettext: 1.07
	p5-Locale-libintl: 1.35
	p5-Text-Unidecode: 1.30
	p5-Unicode-EastAsianWidth: 12.0
	perl5: 5.42.0_1
	pkgconf: 2.4.3,1
	texinfo: 7.1_12,1

Number of packages to be removed: 15

The operation will free 103 MiB.
root@practice:~ # du -sh /usr/ports/distfiles
 42M	/usr/ports/distfiles
root@practice:~ # ls /usr/ports/distfiles
Text-Unidecode-1.30.tar.gz		gettext-0.23.1.tar.xz			libintl-perl-1.35.tar.gz		perl					tree-2.2.1.tgz
Unicode-EastAsianWidth-12.0.tar.gz	gettext-1.07.tar.gz			m4-1.4.19.tar.xz			pkgconf-2.4.3.tar.xz
autoconf-2.72.tar.xz			help2man-1.49.3.tar.xz			nginx-1.28.0.tar.gz			portconfig-0.6.2.tar.bz2
bsddialog-1.1.tar.bz2			libiconv-1.17.tar.gz			pcre2-10.46.tar.bz2			texinfo
root@practice:~ # ls /usr/ports/www/nginx
Makefile		Makefile.extmod		Makefile.options.desc	distinfo		files			pkg-descr		pkg-plist

Which one should you wear?

Wear mittens (pkg) most of the time. They are quick, they are tested together, and updating is pkg upgrade. Reach for gloves (ports) when:

The price of gloves: build time, disk space, the build tools left behind, and you are now responsible for rebuilding when security fixes arrive. Two warnings (from the documentation, not run here): pkg upgrade can replace your custom build with the repository's package when it finds a newer version, so lock the package with pkg lock nginx if you want to keep your gloves; and a big fleet should not build on every server. poudriere builds your own package repository from the ports tree in a clean jail, once, and all your servers then install those custom packages with plain pkg: gloves made once, worn everywhere. The poudriere page does exactly that.

On Linux, the nearest ideas are Gentoo's emerge and Arch's AUR for building, and rpmbuild or apt source for rebuilding a distribution package. On FreeBSD it is simply how the system is built.

⚠️ Do not build on a production server that is short of memory or disk. A big port (a browser, a database) can take hours and gigabytes. Build in a virtual machine or with poudriere, and put the finished package on the server.

Why did the FreeBSD admin compile the web server himself?<br>
The package was one size fits all,<br>
and his hands are not.

πŸ§ͺ Try it yourself

In a FreeBSD virtual machine:

1. Get a ports tree into /usr/ports (from the install disc, or with git). 2. Look at the recipe of a small port such as sysutils/tree, and ask it for its version with make -V PORTVERSION. 3. Run make config in it and look at the options dialog. 4. List its build dependencies, install them as packages with make install-missing-packages, and build it with make install clean BATCH=yes. How long did it take? 5. Check it with pkg info: what is its Origin? 6. Put an option for another port into /etc/make.conf and check it with make showconfig. 7. Compare a ports build with the package version: -V flags if the program has them, and the file sizes. 8. Run pkg autoremove -n. What was left behind?

The cheat sheet

← The pf Firewall: Rules, Tables and Redirects · ↑ FreeBSD Corner · bhyve: Virtual Machines Built into FreeBSD →