FreeBSD in Modern DevOps: Complete Guide
FreeBSD is not the first operating system most DevOps engineers think of. The container ecosystem assumes Linux. Terraform providers default to Ubuntu. CI/CD tutorials start with Docker. But FreeBSD has its own answers to every DevOps requirement -- and in several areas, those answers are technically superior.
Jails predate Docker by over a decade and provide stronger isolation. ZFS gives you atomic snapshots and replication without third-party tools. The ports system builds reproducible packages. The base system ships a complete, auditable operating system. The rc system manages services without the complexity of systemd.
This guide covers how to run a modern DevOps workflow on FreeBSD -- from infrastructure provisioning through deployment, configuration management, CI/CD, and monitoring.
Jails as Containers
FreeBSD jails are the original container technology. They provide process isolation, filesystem isolation, and network isolation at the kernel level. Unlike Docker containers on Linux (which layer namespaces, cgroups, seccomp, and AppArmor to approximate isolation), jails are a single, coherent kernel feature.
Creating Jails for Application Deployment
Using Bastille for jail management:
shpkg install bastille sysrc bastille_enable="YES" bastille bootstrap 14.2-RELEASE update
Create application jails:
shbastille create api-server 14.2-RELEASE 10.0.0.10 bastille create db-server 14.2-RELEASE 10.0.0.11 bastille create cache-server 14.2-RELEASE 10.0.0.12
Install application stacks via templates:
shbastille template api-server myorg/python-api bastille template db-server myorg/postgresql bastille template cache-server myorg/redis
Jail Templates as Infrastructure Code
A Bastille template for a Python API server:
shellPKG python311 py311-pip py311-gunicorn nginx SYSRC nginx_enable=YES CP usr/local/etc/nginx/nginx.conf CMD pip install -r /app/requirements.txt FSTAB /data/api-server/app app nullfs rw 0 0 SERVICE nginx start
Store templates in Git. Version control your infrastructure. Deploy identically across development, staging, and production.
Thin Jails for Density
Thin jails share the base system via nullfs mounts, saving disk space:
shbastille create --thin app1 14.2-RELEASE 10.0.0.20 bastille create --thin app2 14.2-RELEASE 10.0.0.21 bastille create --thin app3 14.2-RELEASE 10.0.0.22
Each thin jail uses only the disk space for its unique files. A server with 50 thin jails uses only marginally more disk than one thick jail. Updates to the base release propagate to all thin jails automatically.
Ansible on FreeBSD
Ansible is the most FreeBSD-compatible configuration management tool. It connects via SSH, requires only Python on the target, and has built-in FreeBSD modules.
Setting Up Ansible
On your control machine:
shpkg install py311-ansible
Create an inventory for your FreeBSD fleet:
shcat > /usr/local/etc/ansible/hosts << 'EOF' [webservers] web1.example.com web2.example.com [databases] db1.example.com [freebsd:children] webservers databases [freebsd:vars] ansible_python_interpreter=/usr/local/bin/python3.11 ansible_become_method=su EOF
FreeBSD-Specific Ansible Modules
Ansible includes modules designed for FreeBSD:
yaml# Install packages with pkgng - name: Install web stack community.general.pkgng: name: - nginx - postgresql16-server - redis state: present # Manage rc.conf settings - name: Enable services community.general.sysrc: name: "{{ item }}_enable" value: "YES" loop: - nginx - postgresql - redis # Manage services - name: Start services ansible.builtin.service: name: "{{ item }}" state: started loop: - nginx - postgresql - redis # Manage PF firewall - name: Deploy PF configuration ansible.builtin.copy: src: pf.conf dest: /etc/pf.conf owner: root mode: '0600' notify: reload pf # Manage jails - name: Create application jail ansible.builtin.command: cmd: bastille create {{ jail_name }} 14.2-RELEASE {{ jail_ip }} creates: /usr/local/bastille/jails/{{ jail_name }}
Ansible Playbook for FreeBSD Server Setup
A complete playbook for provisioning a FreeBSD web server:
shcat > /usr/local/etc/ansible/playbooks/webserver.yml << 'YAML' --- - name: Configure FreeBSD web server hosts: webservers become: true vars: domain: example.com app_user: www pkg_list: - nginx - python311 - py311-gunicorn - git-lite tasks: - name: Update package repository community.general.pkgng: name: pkg state: latest - name: Install packages community.general.pkgng: name: "{{ pkg_list }}" state: present - name: Enable nginx community.general.sysrc: name: nginx_enable value: "YES" - name: Deploy nginx configuration ansible.builtin.template: src: templates/nginx.conf.j2 dest: /usr/local/etc/nginx/nginx.conf notify: reload nginx - name: Deploy PF firewall rules ansible.builtin.template: src: templates/pf.conf.j2 dest: /etc/pf.conf notify: reload pf - name: Start nginx ansible.builtin.service: name: nginx state: started handlers: - name: reload nginx ansible.builtin.service: name: nginx state: reloaded - name: reload pf ansible.builtin.command: cmd: pfctl -f /etc/pf.conf YAML
Run it:
shansible-playbook /usr/local/etc/ansible/playbooks/webserver.yml
CI/CD on FreeBSD
Buildbot
Buildbot is written in Python, runs natively on FreeBSD, and is more flexible than Jenkins for FreeBSD workloads:
shpkg install py311-buildbot py311-buildbot-www py311-buildbot-worker
Configure a FreeBSD build worker:
shbuildbot-worker create-worker /var/buildbot-worker \ buildmaster.example.com:9989 freebsd-worker pass123 sysrc buildbot_worker_enable="YES" service buildbot-worker start
Concourse CI
Concourse supports FreeBSD workers. Install in a jail:
shbastille create ci-server 14.2-RELEASE 10.0.0.50 bastille pkg ci-server install concourse
GitLab Runner
GitLab Runner works on FreeBSD for shell executors:
shpkg install gitlab-runner sysrc gitlab_runner_enable="YES" gitlab-runner register \ --url https://gitlab.example.com/ \ --token YOUR_TOKEN \ --executor shell \ --shell bash service gitlab-runner start
The shell executor runs CI jobs directly on FreeBSD. For isolation, run each GitLab Runner inside its own jail:
shbastille create runner1 14.2-RELEASE 10.0.0.60 bastille pkg runner1 install gitlab-runner git-lite bastille cmd runner1 gitlab-runner register \ --url https://gitlab.example.com/ \ --token YOUR_TOKEN \ --executor shell
CI/CD Pipeline for FreeBSD Projects
A .gitlab-ci.yml for a FreeBSD project:
yamlstages: - build - test - deploy build: stage: build tags: - freebsd script: - make clean - make -j$(sysctl -n hw.ncpu) - make package test: stage: test tags: - freebsd script: - make test - kyua test - kyua report-html --output=test-results/ deploy: stage: deploy tags: - freebsd only: - main script: - bastille cmd production-app "service myapp stop" - cp build/myapp /usr/local/bastille/jails/production-app/root/usr/local/bin/ - bastille cmd production-app "service myapp start"
Terraform and FreeBSD
Terraform provisions infrastructure. FreeBSD support depends on your cloud provider.
DigitalOcean
DigitalOcean has native FreeBSD droplet support:
shpkg install terraform
shell# main.tf terraform { required_providers { digitalocean = { source = "digitalocean/digitalocean" version = "~> 2.0" } } } provider "digitalocean" { token = var.do_token } resource "digitalocean_droplet" "web" { count = 3 name = "web-${count.index + 1}" region = "nyc3" size = "s-2vcpu-4gb" image = "freebsd-14-2-x64" ssh_keys = [var.ssh_fingerprint] connection { type = "ssh" user = "freebsd" host = self.ipv4_address } provisioner "remote-exec" { inline = [ "sudo pkg install -y nginx", "sudo sysrc nginx_enable=YES", "sudo service nginx start" ] } } output "web_ips" { value = digitalocean_droplet.web[*].ipv4_address }
Apply:
shterraform init terraform plan terraform apply
Vultr
Vultr also supports FreeBSD instances natively:
shellresource "vultr_instance" "freebsd" { plan = "vc2-2c-4gb" region = "ewr" os_id = 401 # FreeBSD 14 hostname = "freebsd-server" ssh_key_ids = [vultr_ssh_key.mykey.id] }
Hetzner Cloud
shellresource "hcloud_server" "freebsd" { name = "freebsd-web" server_type = "cx22" image = "freebsd-14.2" location = "fsn1" ssh_keys = ["my-key"] }
Cloud Deployment Patterns
Immutable Infrastructure with ZFS
FreeBSD's ZFS enables immutable deployment patterns without Docker:
sh# Create a golden image bastille create golden-web 14.2-RELEASE 10.0.0.100 bastille template golden-web myorg/web-stack bastille stop golden-web # Snapshot the golden image zfs snapshot zroot/bastille/jails/golden-web@v1.2.3 # Deploy by cloning zfs clone zroot/bastille/jails/golden-web@v1.2.3 zroot/bastille/jails/web-prod-1 zfs clone zroot/bastille/jails/golden-web@v1.2.3 zroot/bastille/jails/web-prod-2 # Roll back a bad deployment zfs rollback zroot/bastille/jails/web-prod-1@v1.2.2
Blue-Green Deployments
sh# Blue is currently active (10.0.0.10) # Deploy green bastille create green-web 14.2-RELEASE 10.0.0.11 bastille template green-web myorg/web-stack-v2 # Test green curl http://10.0.0.11/health # Switch traffic (update PF or load balancer) pfctl -t webservers -T replace 10.0.0.11 # Destroy old blue if green is healthy bastille destroy blue-web bastille rename green-web blue-web
Monitoring
Prometheus and Grafana
shpkg install prometheus grafana sysrc prometheus_enable="YES" sysrc grafana_enable="YES"
Configure Prometheus to scrape FreeBSD targets:
shcat > /usr/local/etc/prometheus.yml << 'EOF' global: scrape_interval: 15s scrape_configs: - job_name: 'node' static_configs: - targets: - 'web1.example.com:9100' - 'web2.example.com:9100' - 'db1.example.com:9100' EOF
Install node_exporter on each FreeBSD host:
shpkg install node_exporter sysrc node_exporter_enable="YES" service node_exporter start
FreeBSD-Specific Monitoring
Monitor jails with custom metrics:
sh# Count running jails jls | tail -n +2 | wc -l # Per-jail resource usage with rctl rctl -u jail:webserver # ZFS pool health zpool status -x zpool list -o name,size,alloc,free,fragmentation,capacity,health
Netdata
Netdata provides real-time monitoring with zero configuration:
shpkg install netdata sysrc netdata_enable="YES" service netdata start
Access the dashboard at http://your-server:19999. Netdata auto-detects FreeBSD services, ZFS pools, network interfaces, and jail resource usage.
Secrets Management
Vault on FreeBSD
HashiCorp Vault runs on FreeBSD:
shpkg install vault sysrc vault_enable="YES"
Configure Vault for production:
shcat > /usr/local/etc/vault/vault.hcl << 'EOF' storage "file" { path = "/var/db/vault/data" } listener "tcp" { address = "0.0.0.0:8200" tls_cert_file = "/usr/local/etc/ssl/vault.crt" tls_key_file = "/usr/local/etc/ssl/vault.key" } api_addr = "https://vault.example.com:8200" EOF service vault start vault operator init
Package Building and Distribution
Poudriere for Custom Packages
Poudriere builds custom FreeBSD packages in clean jail environments:
shpkg install poudriere
Create a build environment:
shpoudriere jail -c -j 142amd64 -v 14.2-RELEASE poudriere ports -c -p default
Create a package list:
shcat > /usr/local/etc/poudriere.d/pkglist << 'EOF' www/nginx databases/postgresql16-server databases/redis lang/python311 sysutils/bastille security/vault EOF
Build:
shpoudriere bulk -j 142amd64 -p default -f /usr/local/etc/poudriere.d/pkglist
Poudriere produces a complete pkg repository that you can serve via nginx and point your FreeBSD servers at. This gives you reproducible package builds with custom options -- the FreeBSD equivalent of building custom Docker images.
GitOps Workflow
A complete GitOps workflow on FreeBSD:
- Infrastructure definitions (Terraform) in Git
- Server configurations (Ansible playbooks) in Git
- Jail templates (Bastille templates) in Git
- Application code in Git
- CI/CD (GitLab Runner on FreeBSD) builds, tests, and deploys
sh# Developer pushes code git push origin main # GitLab CI runs on FreeBSD runner # -> Builds application # -> Runs tests in a jail # -> Deploys to production jail # Infrastructure changes terraform apply # Provisions/modifies FreeBSD servers ansible-playbook site.yml # Configures servers bastille template production-app myorg/app # Deploys app
This is the same workflow that Linux/Docker shops use, adapted for FreeBSD's native tooling. The key difference: jails replace containers, ZFS replaces volume drivers, Bastille templates replace Dockerfiles, and Poudriere replaces Docker Hub.
FAQ
Can FreeBSD replace Linux in a DevOps workflow?
For most server workloads, yes. The tools exist: Ansible, Terraform, Prometheus, Grafana, GitLab Runner, Vault, and more all run on FreeBSD. The main gaps are Docker (not available on FreeBSD) and Kubernetes (which requires Linux). If your workflow is Docker/K8s-centric, FreeBSD requires rethinking. If your workflow is VM or bare-metal-centric, FreeBSD fits naturally.
Is Ansible the best config management tool for FreeBSD?
Ansible has the best FreeBSD support among popular config management tools. Puppet and Chef work but have smaller FreeBSD communities. Salt works but its FreeBSD modules are less complete. Ansible's agentless architecture (just SSH + Python) makes it the simplest to deploy on FreeBSD.
How do I run Docker on FreeBSD?
You do not. Docker requires the Linux kernel. On FreeBSD, use jails for container-like isolation. If you absolutely need Docker, run it inside a Linux VM on bhyve. But in most cases, jails provide better isolation and performance than Docker containers.
Can Terraform provision FreeBSD servers on AWS?
AWS does not offer official FreeBSD AMIs, but the FreeBSD project publishes community AMIs for EC2. You can reference these in Terraform. However, DigitalOcean, Vultr, and Hetzner have better first-party FreeBSD support and are easier to use with Terraform.
How do I handle secrets in FreeBSD jails?
Use Vault for centralized secrets management. Applications in jails authenticate to Vault using AppRole or token-based auth. For simpler setups, use Ansible Vault to encrypt secrets in your playbooks and deploy them to jails during provisioning.
Is there a FreeBSD equivalent of Kubernetes?
Not directly. The closest pattern is using a jail orchestrator (Bastille, CBSD) with a load balancer (HAProxy, relayd) and ZFS for storage. For service discovery, use DNS. This lacks Kubernetes' automatic scheduling and self-healing but covers the deployment and scaling needs of most workloads. Nomad by HashiCorp runs on FreeBSD and provides some orchestration capabilities.