FreeBSD.software
Home/Guides/How to Run Docker-Like Workflows on FreeBSD
tutorial·2026-04-09·8 min read

How to Run Docker-Like Workflows on FreeBSD

How to replicate Docker workflows on FreeBSD using jails, ZFS, Bastille templates, pot with Nomad, and OCI image imports for container-like deployments.

How to Run Docker-Like Workflows on FreeBSD

Docker does not run natively on FreeBSD. There is no Docker daemon, no containerd, no runc. The Linux kernel features Docker depends on -- cgroups v2, namespaces, OverlayFS -- do not exist on FreeBSD. This is not a bug or an oversight. FreeBSD has its own isolation primitives that predate Docker by over a decade, and they work differently.

This guide shows you how to achieve the same operational workflows Docker provides -- image-based deployment, reproducible environments, layered filesystems, orchestration -- using FreeBSD-native tools. You will not miss Docker once you understand what jails, ZFS, Bastille, and pot can do.

For background on FreeBSD jails, see our FreeBSD jails guide. For jail manager comparisons, see best jail manager for FreeBSD.

Why Docker Does Not Run on FreeBSD

Docker is deeply tied to Linux kernel interfaces. Specifically:

  • Namespaces (PID, network, mount, user, UTS, IPC) -- FreeBSD uses jails, which provide similar but not identical isolation
  • cgroups -- FreeBSD uses rctl(8) for resource limits
  • OverlayFS / UnionFS -- FreeBSD uses ZFS clones and nullfs mounts
  • seccomp-bpf -- FreeBSD uses capsicum(4) and mac(4)

There was a Docker port attempt years ago via linux_base compatibility, but it was abandoned. Bhyve-based solutions exist (running a Linux VM to host Docker), but that defeats the purpose of native container workflows.

The real answer is: FreeBSD has better primitives. You just need the right tools to compose them.

Jails + ZFS: The Foundation

FreeBSD jails are the native isolation mechanism. Combined with ZFS, they provide everything Docker containers offer and more.

Creating a Base Template with ZFS

This is the equivalent of a Docker base image:

sh
zfs create -o mountpoint=/jails zroot/jails zfs create zroot/jails/templates fetch https://download.freebsd.org/releases/amd64/14.2-RELEASE/base.txz -o /tmp/base.txz mkdir -p /jails/templates/base-14.2 tar -xf /tmp/base.txz -C /jails/templates/base-14.2 zfs snapshot zroot/jails/templates/base-14.2@clean

Cloning Jails Instantly (Like Docker Run)

ZFS clones are instant, copy-on-write copies. This is faster than Docker's layer system:

sh
zfs clone zroot/jails/templates/base-14.2@clean zroot/jails/running/webserver-01 cat >> /etc/jail.conf <<'EOF' webserver-01 { host.hostname = "webserver-01"; path = "/jails/running/webserver-01"; ip4.addr = "lo1|10.0.0.10/32"; mount.devfs; exec.start = "/bin/sh /etc/rc"; exec.stop = "/bin/sh /etc/rc.shutdown"; allow.raw_sockets; } EOF service jail start webserver-01

Snapshotting (Like Docker Commit)

After configuring a jail, snapshot it to create a reusable image:

sh
service jail stop webserver-01 zfs snapshot zroot/jails/running/webserver-01@configured zfs clone zroot/jails/running/webserver-01@configured zroot/jails/running/webserver-02

Destroying Jails (Like Docker rm)

sh
service jail stop webserver-01 zfs destroy zroot/jails/running/webserver-01

Clean. Instant. No leftover layers or dangling volumes.

Bastille: Dockerfile-Like Templates

Bastille is the closest thing FreeBSD has to a Docker-like workflow. It wraps jails and ZFS with a template system that works like a Dockerfile.

Installing Bastille

sh
pkg install bastille sysrc bastille_enable="YES" sysrc cloned_interfaces="lo1" sysrc ifconfig_lo1_name="bastille0" service netif cloneup

Configure Bastille for ZFS:

sh
# /usr/local/etc/bastille/bastille.conf bastille_zfs_enable="YES" bastille_zfs_zpool="zroot" bastille_release="14.2-RELEASE"

Bootstrapping a Release (Like Docker Pull)

sh
bastille bootstrap 14.2-RELEASE update

This downloads and extracts the FreeBSD base system. It is your base image.

Creating a Jail (Like Docker Run)

sh
bastille create webserver 14.2-RELEASE 10.0.0.10 bastille start webserver

Bastille Templates (Like Dockerfiles)

Create a Bastillefile at /usr/local/bastille/templates/myorg/nginx/Bastillefile:

sh
PKG nginx SYSRC nginx_enable=YES CP usr/local/etc/nginx/nginx.conf /usr/local/etc/nginx/nginx.conf CP usr/local/www/mysite /usr/local/www/mysite SERVICE nginx start

Apply the template:

sh
bastille template webserver myorg/nginx

You can also pull templates from Git repositories:

sh
bastille template webserver https://gitlab.com/bastillebsd-templates/nginx

Exporting and Importing (Like Docker Save/Load)

sh
bastille export webserver bastille import /usr/local/bastille/backups/webserver.xz

Listing and Managing (Like Docker ps)

sh
bastille list bastille console webserver bastille cmd webserver pkg upgrade -y bastille restart webserver bastille destroy webserver

Pot + Nomad: Orchestrated Deployments

Pot is another jail manager, but it was designed specifically to work with HashiCorp Nomad for orchestration. If you need multi-host scheduling (the FreeBSD equivalent of Docker Swarm or Kubernetes), this is the path.

Installing Pot

sh
pkg install pot sysrc pot_enable="YES" pot init

Creating a Pot (Jail)

sh
pot create -p nginx-pot -t single -b 14.2 -N public-bridge -i auto pot start nginx-pot pot run nginx-pot -c "pkg install -y nginx" pot snap nginx-pot

Pot Flavours (Like Docker Images)

Flavours are scripts that run on first boot to configure a pot. Create one at /usr/local/etc/pot/flavours/nginx.sh:

sh
#!/bin/sh pkg install -y nginx sysrc nginx_enable="YES" service nginx start

Apply during creation:

sh
pot create -p web01 -t single -b 14.2 -N public-bridge -i auto -f nginx pot start web01

Nomad Integration

Install the Nomad pot driver:

sh
pkg install nomad pot-driver-nomad

Create a Nomad job file /usr/local/etc/nomad/jobs/web.hcl:

sh
cat > /usr/local/etc/nomad/jobs/web.hcl <<'EOF' job "web" { datacenters = ["dc1"] type = "service" group "web" { count = 3 task "nginx" { driver = "pot" config { image = "https://potrepo.example.com/nginx-pot_14.2.xz" pot = "nginx-pot" tag = "1.0" command = "/usr/local/sbin/nginx" } resources { cpu = 500 memory = 256 } } } } EOF nomad job run /usr/local/etc/nomad/jobs/web.hcl

This gives you multi-host scheduling of FreeBSD jails, equivalent to running Docker containers under Kubernetes or Nomad on Linux.

Running OCI/Docker Images on FreeBSD

FreeBSD 14 ships with containerd support via the runj OCI runtime. This allows running FreeBSD-based OCI images (not Linux images) using standard container tooling.

Installing the OCI Stack

sh
pkg install containerd runj buildkit nerdctl

Running a FreeBSD OCI Image

sh
sysrc containerd_enable="YES" service containerd start nerdctl pull docker.io/dougrabson/freebsd-minimal:14 nerdctl run --rm -it docker.io/dougrabson/freebsd-minimal:14 /bin/sh

Building FreeBSD OCI Images

Create a Containerfile:

sh
FROM docker.io/dougrabson/freebsd-minimal:14 RUN pkg install -y nginx COPY nginx.conf /usr/local/etc/nginx/nginx.conf CMD ["/usr/local/sbin/nginx", "-g", "daemon off;"]

Build with buildkit:

sh
nerdctl build -t myregistry/freebsd-nginx:latest . nerdctl push myregistry/freebsd-nginx:latest

This is genuine OCI compatibility -- your images can be stored in any container registry and pulled by any OCI-compliant runtime that supports FreeBSD.

Docker Compose Equivalent: Bastille + Scripts

For multi-service stacks (the Docker Compose use case), combine Bastille with a shell script:

sh
#!/bin/sh # deploy-stack.sh -- equivalent to docker-compose up RELEASE="14.2-RELEASE" NETWORK="10.0.0" # Database bastille create db ${RELEASE} ${NETWORK}.10 bastille template db myorg/postgresql # Cache bastille create cache ${RELEASE} ${NETWORK}.11 bastille template cache myorg/redis # Web application bastille create web ${RELEASE} ${NETWORK}.12 bastille template web myorg/myapp # Reverse proxy bastille create proxy ${RELEASE} ${NETWORK}.13 bastille template proxy myorg/nginx-proxy echo "Stack deployed." bastille list

Tear it all down:

sh
#!/bin/sh # destroy-stack.sh -- equivalent to docker-compose down for jail in proxy web cache db; do bastille destroy "$jail" done

Comparison: Docker vs FreeBSD Equivalents

| Docker Concept | FreeBSD Equivalent |

|---|---|

| docker pull | bastille bootstrap / pot prepare |

| docker run | bastille create + start / pot create + start |

| docker build (Dockerfile) | Bastille templates / pot flavours |

| docker commit | zfs snapshot |

| docker push/pull | bastille export/import / pot image repos |

| docker-compose | Shell scripts with Bastille/pot |

| docker rm | bastille destroy / pot destroy |

| Docker Swarm/K8s | Nomad + pot driver |

| OverlayFS layers | ZFS clones (copy-on-write) |

| cgroups resource limits | rctl(8) |

| Docker volumes | ZFS datasets / nullfs mounts |

| Docker networking | VNET + epair + PF |

| OCI images | runj + containerd + nerdctl |

Performance Considerations

FreeBSD jails have lower overhead than Docker containers on Linux. There is no container runtime daemon consuming resources. Jails run as native processes with filesystem isolation -- there is no layer of indirection.

ZFS clones are instantaneous regardless of dataset size. Docker image pulls download layers; ZFS clones reference existing data blocks. For local development and deployment, jails spin up faster.

The tradeoff is ecosystem. Docker has Docker Hub with millions of images. FreeBSD has a smaller image ecosystem. You will build more of your own templates, but you will understand exactly what is in them.

FAQ

Can I run Linux Docker containers on FreeBSD?

Not natively. You can run a Linux VM in bhyve and run Docker inside that VM, but this adds a full virtualization layer. For production, use native FreeBSD jails instead.

Is Bastille or pot better for Docker-like workflows?

Bastille is closer to the Docker CLI experience and better for single-host workflows. Pot is better if you need Nomad orchestration across multiple hosts.

Can I use Docker Hub images on FreeBSD?

Only FreeBSD-based OCI images work with runj/containerd. Linux-based Docker Hub images will not run. The FreeBSD OCI image ecosystem is small but growing.

How do I handle persistent storage like Docker volumes?

Use ZFS datasets mounted into jails via nullfs or by setting the dataset mountpoint inside the jail path. This is more flexible and performant than Docker volumes.

Is there a docker-compose equivalent for FreeBSD?

Not a direct equivalent. Use shell scripts with Bastille or pot commands, or use Nomad for declarative multi-service deployments.

Can I migrate my Docker workflow to FreeBSD jails?

Yes, but it requires rewriting Dockerfiles as Bastille templates or pot flavours. The concepts map directly -- base images become bootstrapped releases, layers become ZFS snapshots, and networking becomes VNET. The effort scales with how many custom images you maintain.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.