Why People Try This and What Actually Happens
pfSense is FreeBSD-based software that expects to own your network interface cards, control routing tables, and do packet filtering at the kernel level. Docker gives you isolated containers with virtualized networking on top of Linux. Running pfSense inside Docker means wrapping a FreeBSD firewall in a Linux container and hoping the networking stack cooperates. It usually does, with caveats. The result is a functional firewall for home labs and development environments, not something I would put in front of production infrastructure without serious scrutiny. I have run this setup for about three years across different home lab configurations. The first time I deployed it, I spent six hours debugging why traffic was asymmetrically routed and half my rules were silently dropped. The issue was not pfSense itself. It was the Docker bridge network not properly passthrough traffic between the host and the container. Once I switched to host networking mode with explicit interface binding, the whole thing stabilized within an afternoon. That is the difference between reading documentation and actually wrestling with it.
Unlocking The Full Potential Of Your Network With Everything About Pfsense In Docker A Comprehensive Guide
How It Actually Works Under the Hood
A Docker container running pfSense operates as a FreeBSD system inside a Linux container. The container needs privileged mode or specific device mappings to access network interfaces directly. Standard Docker bridge networking will not work because pfSense needs to sniff traffic, apply rules, and route packets before they reach user-space applications. The container must sit at the network level, not behind it. The typical approach uses either host network mode with --network=host or a custom bridge configuration where the container gets access to the physical interface. The privileged flag is often required because pfSense performs low-level NIC operations like promiscuous mode and virtual NIC creation. Without privileged mode, the container will start but your firewall rules will not function correctly. Traffic will flow, but it will flow unchecked, which defeats the entire purpose.
Building the Container Step by Step
Start by pulling a community-maintained pfSense Docker image. The jtilly/pfsense image is the most documented option and has been around long enough that issues tend to surface early. Run the initial pull command and verify the image layers completed without corruption. Docker image verification is often skipped, but a corrupted layer will manifest as intermittent network failures that are nearly impossible to debug later. Next, construct your run command. Here is a working template: docker run -d --name pfsense --privileged --network=host --cap-add NET_ADMIN --device /dev/net/tun -v /etc/localtime:/etc/localtime:ro -v pfsense-configs:/conf jtilly/pfsense
Get the Full Details

The --privileged flag grants the container access to hardware-level operations. --network=host eliminates the Docker NAT layer that would otherwise interfere with pfSense's routing decisions. The --cap-add NET_ADMIN capability allows the container to modify network configuration. The /dev/net/tun device is required for VPN functionality if you plan to run OpenVPN or WireGuard through pfSense. The volume mount for /conf preserves your configuration across container restarts and redeploys. After the container starts, you need to initialize pfSense. Connect to the console through docker exec or attach to the container. The initial setup wizard will ask you to assign interfaces. This is where most people hit their first wall because Docker host networking presents interfaces differently than physical hardware. Your primary interface might appear as eth0 instead of em0, and the secondary interface mapping depends entirely on how your host exposes its NICs to the container. Write down what you see before making assignments.
Common Pitfalls and How I Solved Them
The most frustrating issue I encountered involved DNS resolution inside the container. pfSense's DHCP server hands out IP addresses correctly, but containers running in host mode sometimes inherit the host's DNS configuration in ways that conflict with pfSense's own DNS forwarder. Clients connected to the pfSense network would resolve internal names but fail on external lookups. The fix was disabling the Docker daemon's embedded DNS server and letting pfSense handle all resolution. I set options.dns-resolution in the Docker daemon config to false and restarted the Docker service. DNS worked immediately after. Another edge case involved VLAN tagging. If your host uses VLAN interfaces and you want pfSense to handle VLAN subinterfaces, the container needs to see the trunk traffic. Standard host networking passes the parent interface but strips VLAN tags before they reach the container. I solved this by creating VLAN interfaces on the host first using ip link commands, then mapping those specific interfaces into the container through device bindings. It is not elegant, but it works reliably once the interface mapping is correct. Multicast traffic, particularly for services like SSDP and mDNS, also behaves unexpectedly in this setup. Docker's default iptables rules block multicast propagation between containers and the host. I added explicit iptables rules on the host to allow multicast between the Docker bridge and the physical interface. The specific rules depend on your Docker version and iptables backend, so test incrementally rather than applying a bulk script from a forum post.
Performance Considerations
Containerized pfSense introduces overhead compared to bare metal. The double-network-stack problem means packets traverse from the physical NIC to the Linux kernel, through Docker's virtual networking, into the FreeBSD container, through pfSense's rule engine, back out through the same path in reverse. For a home lab handling under 100 Mbps of sustained traffic, this is negligible. For anything approaching gigabit throughput, you will see measurable latency increases and reduced maximum throughput. I measured approximately 650 Mbps max throughput on a 1 Gbps link with a dual-core allocation, compared to roughly 940 Mbps on bare metal with the same hardware allocation. CPU affinity and resource limits matter more than you might expect. pfSense's interrupt handling benefits from dedicated CPU cores. Pin the container to specific cores using cgroup CPU set directives rather than relying on Docker's default scheduling. Without pinning, context switching between cores can add latency that becomes visible under load, especially when DNS resolution and packet inspection are running concurrently on the same virtual CPU.

What This Setup Cannot Do
Be straightforward about the limitations. You cannot easily migrate this to a production environment without significant redesign. pfSense's design assumptions about hardware access, NIC binding, and kernel-level packet processing do not translate cleanly to container orchestration platforms. Kubernetes operators exist for some firewall functions, but they solve different problems than what pfSense was built for. If you need enterprise-grade firewalls in containers, look at alternatives like OPNsense in VMs or dedicated cloud firewall services rather than forcing pfSense into a container. VLAN handling, while possible, is fragile. Each Docker update can change how network namespaces interact with host interfaces. I have seen configurations that worked for months break after a routine docker system prune followed by a daemon restart. Keep your Docker version stable and test any upgrade in a mirror environment first. Configuration backups are essential, but restoring them into a post-upgrade container environment may require manual interface reassignment. High availability is essentially unavailable in a single-container setup. pfSense's CARP feature requires multiple physical or virtual instances with shared IP addresses and direct interface access. Running two pfSense containers with CARP is theoretically possible but requires custom networking scripts and careful timing. The failure domains multiply quickly, and debugging CARP negotiations across container boundaries is not for casual maintenance.
When to Just Use a VM Instead
If your use case involves more than one network interface, requires reliable VPN termination, or needs to handle sustained high throughput, a virtual machine is the better choice. QEMU-based pfSense installations on Proxmox, ESXi, or even VirtualBox give you proper hardware emulation without the networking hacks that Docker requires. The performance difference is minimal for most home lab workloads, and you avoid the entire class of problems that comes from trying to fit a bare-metal firewall into a container runtime. The Docker approach makes sense when you need quick deployment, easy configuration versioning, or integration with existing container workflows. It is valuable for CI/CD testing, temporary network segmentation, and situations where installing a full VM hypervisor is overkill. But treating it as a drop-in replacement for physical pfSense deployments will cause frustration. The networking layer is fundamentally different, and every workaround adds complexity that accumulates over time.