The convergence of network security, infrastructure automation, and containerized services has created a new paradigm for homelab enthusiasts and small business network administrators. At the heart of this modern architecture lies OPNsense, a robust, open-source firewall and routing distribution based on FreeBSD. While OPNsense excels at packet filtering, NAT, and DHCP, its native package ecosystem, rooted in the FreeBSD ports system, often presents limitations for users seeking to run modern, Linux-centric applications. The solution for many advanced users is not to force software onto the firewall appliance itself, but to construct a hybrid infrastructure where OPNsense handles the perimeter and core network services, while a separate Docker host manages application workloads such as DNS sinkholes, password managers, and monitoring tools. This approach leverages the strengths of each technology: the hardened, stable networking stack of OPNsense and the flexibility, isolation, and ease of deployment provided by Docker containers. By integrating these distinct systems through careful configuration of DHCP, DNS forwarding, and infrastructure-as-code tools like Ansible, administrators can build a resilient, scalable, and secure network environment that is both manageable and highly performant.
The Philosophy of Hybrid Infrastructure
The decision to separate firewall duties from application hosting is a critical architectural choice that impacts both security and stability. OPNsense is designed primarily as a gateway device. Its underlying operating system, FreeBSD, is renowned for its network stack performance and reliability. However, FreeBSD lacks the vast repository of pre-compiled binaries and the widespread community support for containerization that Linux distributions enjoy. Running complex applications like Bitwarden, Pi-hole, or AdGuard Home directly on OPNsense is often fraught with compatibility issues, potential conflicts with the core firewall services, and a higher risk of destabilizing the entire network gateway.
Conversely, Docker provides a lightweight, efficient method for running applications in isolated environments. By dedicating a separate machine—whether a high-performance NAS, a spare Intel Xeon server, or a virtual machine—to run Docker, administrators can leverage the full power of Linux-based containers without compromising the integrity of their firewall. This separation of concerns ensures that if a container crashes or requires a restart, the core network routing and firewall rules remain unaffected. Furthermore, it allows for easier updates and backups of application configurations, as Docker volumes can be managed independently of the firewall's configuration database.
This hybrid model is particularly appealing to users who have migrated from consumer-grade routers to professional-grade hardware. For instance, a user might replace a simple TP-Link router with a dedicated box running OPNsense natively, perhaps due to specific hardware compatibility needs like Intel i226-V NICs, which are well-supported by OPNsense but might have been problematic on other platforms. Behind this firewall, a managed switch connects to various endpoints, including a QNAP NAS or a dedicated Linux server that hosts the Docker containers. This setup provides the robustness of enterprise networking with the flexibility of modern cloud-native application deployment.
Running OPNsense in Docker: A Specialized Use Case
While the recommended architecture involves running Docker on a separate host from OPNsense, there are specific scenarios where running OPNsense itself within a Docker container is desirable. This approach is primarily suited for testing, development, and simulation environments rather than production deployments. Running OPNsense in Docker allows network engineers to spin up firewall instances rapidly, test configuration changes, or simulate network topologies without dedicating physical hardware or full virtual machines.
One notable example of this is the Docker image provided by koichirok, which packages OPNsense for containerized execution using KVM. This image is explicitly intended for testing purposes and is not designed for production use. The complexity of running a firewall in a container arises from the need for deep kernel-level access to network interfaces and virtualization resources. To achieve this, the Docker container must be granted specific capabilities and access to device files.
The technical requirements for running this specific OPNsense Docker image are stringent. The container must be started with the --cap_add NET_ADMIN option to allow network administration privileges within the container. Additionally, it requires direct access to the host's KVM device via --device /dev/kvm. For systems with newer cgroup versions, a specific cgroup rule --device-cgroup_rule "c 10:232 rwm" is also necessary to ensure proper device access. The image size is approximately 835.6 MB, reflecting the substantial footprint of the OPNsense operating system. The most recent version referenced, koichirok/docker-opnsense:25.1.3-image, indicates an active development cycle, though the base image was last updated about a year ago prior to the latest patch.
Another variation exists in the form of the demisto/opnsense image, created by Demisto, a Palo Alto Networks company. This image is significantly smaller, at 33.2 MB, suggesting it may be a stripped-down version or a specific tool for security analysis rather than a full firewall appliance. It requires Docker Desktop 4.37.1 or later, highlighting the dependency on modern container runtime features. These images serve niche purposes, such as security research or temporary lab setups, but they underscore the technical feasibility of containerizing FreeBSD-based firewalls when the right privileges are granted.
yaml
version: "3"
services:
opnsense:
image: koichirok/docker-opnsense:24.1
devices:
- /dev/kvm
cap_add:
- NET_ADMIN
ports:
- 10443:443
stop_grace_period: 2m
The above Docker Compose configuration illustrates the necessary parameters for running the koichirok OPNsense image. The mapping of port 10443 on the host to port 443 in the container allows access to the OPNsense web interface. The stop_grace_period of 2 minutes ensures that the container has sufficient time to shut down the firewall services cleanly, preventing potential filesystem corruption or configuration loss. However, it is crucial to remember that this setup lacks the full hardware acceleration and network interface flexibility of a bare-metal or hypervisor-based installation, making it unsuitable for handling real-world traffic loads or serving as a primary gateway.
OPNsense as the Network Foundation
For production environments, OPNsense is best deployed on bare metal or as a virtual machine on a hypervisor like Proxmox or ESXi. This deployment model provides the necessary performance and stability for handling all network traffic. The initial step in building a hybrid homelab is to install and configure OPNsense as the central router and firewall. The official installation image, available from the OPNsense website, should be the latest stable version for the amd64 architecture. This ensures access to the newest features, security patches, and hardware drivers.
Once installed, OPNsense takes over the critical network functions. It acts as the DHCP server, assigning IP addresses to all devices on the local network. It also handles DNS resolution, either by acting as a recursive resolver itself using the built-in Unbound service or by forwarding queries to upstream DNS servers. In a traditional setup, OPNsense would also provide ad-blocking capabilities through its built-in Unbound or by forwarding DNS to a dedicated server. However, in the hybrid model, OPNsense's role evolves. It remains the authority for DHCP and the gateway for all traffic, but it delegates DNS resolution and ad-blocking to a separate Docker host.
The configuration of OPNsense in this scenario involves careful adjustment of its DNS settings. By default, OPNsense may use dnsmasq for local DNS caching. In the hybrid architecture, dnsmasq is often disabled in favor of Unbound for recursive resolution, or OPNsense is configured to forward all DNS queries to the IP address of the Docker host running the DNS sinkhole. This ensures that all ad-blocking and tracker-blocking logic is handled centrally by the Docker container, while OPNsense manages the distribution of this DNS IP to client devices via DHCP.
Docker Host Preparation and Application Deployment
The Docker host serves as the application layer of the homelab. This could be a high-performance NAS, such as a QNAP or Synology device, or a dedicated Linux server running Ubuntu or Debian. The key requirement is that this host has spare computational resources and runs a Linux distribution that supports Docker and Docker Compose. In one documented scenario, a user leveraged a high-performance NAS server running Ubuntu 20.04.5 with an Intel Xeon processor. The ample headroom on this server allowed it to run Docker containers efficiently while still performing its primary NAS functions.
The preparation of the Docker host involves installing Docker and Docker Compose. Once the environment is ready, the next step is to deploy the desired applications. Two popular choices for DNS sinkholes in this context are Pi-hole and AdGuard Home. Both offer web interfaces for management, ad-blocking capabilities, and integration with various upstream DNS providers.
Pi-hole is a widely used DNS sinkhole that blocks ads and trackers at the DNS level. It is lightweight and has a large community support base. To deploy Pi-hole in Docker, a specific directory structure must be created to store its configuration and data. The directories ~/docker/pihole/etc-pihole and ~/docker/pihole/etc-dnsmasq.d are created to ensure data persistence across container restarts.
yaml
version: "3"
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
environment:
TZ: 'Europe/Berlin'
WEBPASSWORD: 'StrongPasswordHere'
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
volumes:
- ./etc-pihole:/etc/pihole
- ./etc-dnsmasq.d:/etc/dnsmasq.d
The Docker Compose file for Pi-hole specifies the image, container name, restart policy, environment variables for time zone and web interface password, and the necessary port mappings. Ports 53 for TCP and UDP are exposed for DNS traffic, and port 80 is exposed for the web interface. The volumes map the local directories to the corresponding paths inside the container, ensuring that configuration changes are saved. After deploying the container with docker compose up -d, the web interface becomes accessible at http://192.168.10.5/admin, assuming the Docker host has the IP address 192.168.10.5.
AdGuard Home is another robust option, offering similar ad-blocking features along with DNS-over-HTTPS and DNS-over-TLS support. Its deployment is similarly straightforward but involves different directory structures and port configurations. The directories ~/docker/adguard/work and ~/docker/adguard/conf are used to store the work and configuration files, respectively.
yaml
version: '3'
services:
adguardhome:
image: adguard/adguardhome:latest
container_name: adguardhome
restart: unless-stopped
network_mode: host
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
In this configuration, AdGuard Home uses network_mode: host to bind directly to the host's network interfaces, which can improve performance and simplify port management. After deployment, the web interface is accessible at http://192.168.10.5:3000. The choice between Pi-hole and AdGuard Home often comes down to personal preference, specific feature requirements, and familiarity with the interface. Both solutions integrate seamlessly into the hybrid architecture by acting as the primary DNS resolver for the network.
Integrating OPNsense and the Docker DNS Sinkhole
The integration of OPNsense and the Docker-based DNS sinkhole is the critical step that ties the entire architecture together. This integration ensures that all devices on the local network use the DNS sinkhole for resolution, thereby benefiting from ad-blocking and tracker filtering. The process involves configuring the DHCP server in OPNsense and adjusting the DNS settings to forward queries to the Docker host.
First, the IP address of the Docker host must be static to ensure consistent connectivity. This can be achieved by reserving an IP address for the Docker host's MAC address in the OPNsense DHCP server or by configuring a static IP on the Docker host itself. In the examples provided, the Docker host is assigned the IP address 192.168.10.5.
Next, the DHCP server configuration in OPNsense must be updated. The DNS server entry in the DHCP options should be set to the IP address of the Docker host (192.168.10.5). This ensures that all clients receiving IP addresses from OPNsense are also configured to use the Docker host as their primary DNS server. Additionally, the DHCP service in OPNsense may need to be adjusted depending on whether the Docker host is also providing DHCP services. In many setups, OPNsense remains the sole DHCP server, distributing the IP address of the Docker host as the DNS server. This centralized management simplifies network administration and ensures consistent DNS configuration across all devices.
Furthermore, the DNS settings in OPNsense itself must be configured to use the Docker host as an upstream DNS server. This is crucial because the OPNsense appliance also needs to resolve domain names for its own operations, such as fetching updates or accessing web services. By setting the Docker host as the DNS server in the general settings of OPNsense, the firewall itself benefits from the ad-blocking and caching provided by the sinkhole. If OPNsense was previously using dnsmasq for local DNS caching, this service should be disabled to prevent conflicts and ensure that all DNS queries are forwarded to the Docker host.
Infrastructure Automation with Ansible
While manual configuration is feasible for small homelabs, scaling this architecture and ensuring consistency across multiple devices benefits greatly from automation. Ansible, a powerful configuration management and automation tool, can be used to deploy and manage the Docker host and OPNsense configurations. This Infrastructure-as-Code approach ensures that the entire environment can be reproduced, updated, and backed up automatically.
To begin, Ansible must be installed on a control machine, typically a Linux workstation or server. The command sudo apt install ansible git -y installs Ansible and Git, which are necessary for managing playbooks and version control. Once installed, a project directory structure is created to organize the automation scripts.
text
homelab-ansible/
├── inventory.yml
├── roles/
│ ├── docker_host/
│ │ └── tasks/main.yml
│ └── opnsense_backup/
│ └── tasks/main.yml
└── site.yml
The inventory.yml file defines the hosts that Ansible will manage. It includes groups for the Docker host and the OPNsense host, specifying their IP addresses and SSH users.
yaml
docker_host:
hosts:
docker01:
ansible_host: 192.168.10.5
ansible_user: youruser
opnsense_host:
hosts:
firewall:
ansible_host: 192.168.10.1
ansible_user: root
The site.yml file serves as the entry point for the automation, defining which roles to apply to which host groups.
yaml
- hosts: docker_host
roles:
- docker_host
- hosts: opnsense_host
roles:
- opnsense_backup
The docker_host role can include tasks to copy configuration files for Docker Compose and restart the containers. This ensures that any changes to the Pi-hole or AdGuard Home configurations are applied automatically. The opnsense_backup role can include tasks to back up the OPNsense configuration, ensuring that the firewall settings are preserved and can be restored in case of failure.
yaml
- name: Copy config files and restart containers
copy:
src: ~/docker/
dest: ~/docker/
notify: restart docker compose
By using Ansible, administrators can reduce the risk of human error, simplify the deployment of new services, and ensure that the entire homelab infrastructure is consistent and up-to-date. This automation is particularly valuable in environments where multiple devices and services are interdependent, as it allows for coordinated updates and configuration changes.
Real-World Challenges and Solutions
Implementing a hybrid OPNsense and Docker architecture is not without its challenges. Users have reported various issues during the initial setup, ranging from PXE booting problems to DNS resolution errors. One common challenge is ensuring that the Docker host can communicate effectively with OPNsense, especially if they are on different subnets or if firewall rules block necessary traffic. In such cases, it is essential to verify that the OPNsense firewall allows outbound DNS traffic to the Docker host and that the Docker host can receive responses.
Another challenge is the configuration of PXE booting, which relies on specific DNS and DHCP options. Users have noted that getting PXE to work with Pi-hole required careful tweaking of dnsmasq configurations within the Pi-hole container. This highlights the importance of understanding the underlying mechanisms of each service and how they interact. By referencing community forums and documentation, users can find solutions to these specific issues, such as adjusting the DNS search domains or enabling specific DHCP options.
Performance is another consideration. While Docker containers are lightweight, running multiple services on a single host can consume significant resources. It is important to monitor the CPU and memory usage of the Docker host to ensure that it has sufficient headroom. In the case of the Intel Xeon NAS, the high performance of the hardware allowed it to handle the additional load without noticeable degradation in service. For less powerful devices, it may be necessary to prioritize which services run in Docker and which run natively.
Conclusion
The integration of OPNsense with Dockerized applications represents a powerful and flexible approach to building a modern homelab or small business network. By leveraging the strengths of each technology—OPNsense for robust firewall and routing, and Docker for flexible application deployment—administrators can create a resilient infrastructure that is both secure and easy to manage. The separation of concerns ensures that the core network functions remain stable, while the application layer can be updated and scaled independently. Automation tools like Ansible further enhance this architecture by providing consistency, repeatability, and ease of management. While challenges may arise during initial setup, the wealth of community knowledge and documentation available makes it possible to overcome these hurdles. Ultimately, this hybrid model offers a best-of-both-worlds solution, combining the reliability of enterprise-grade networking with the agility of cloud-native technologies.