NxValue Stack

LXC vs Docker: System Containers vs Application Containers for a Private DNS Stack

The short answer

Docker and LXC are both container technologies, and both share your host's kernel. The difference is the philosophy:

For a DNS filter, which is a small, always-on, network-facing service, both work well. A rough rule of thumb:

The rest of this guide explains why, with the DNS-specific details that tend to cause trouble.


What a container actually is

A container is not a small virtual machine. There is no second kernel. A container is an ordinary process (or group of processes) on the host that the kernel has been told to isolate, using two main features:

Because the kernel is shared, containers start in about a second, use very little extra memory, and run at essentially native speed. The trade-off is that isolation is weaker than a VM's: a serious kernel vulnerability can affect every container on the host. We come back to this in the security section.

Both Docker and LXC are built on these same kernel features. They differ in what they put inside the isolated space and in how you are expected to manage it.


Docker: the application container

Docker packages one application plus its dependencies into an image. Images are built in read-only layers, which makes them easy to share and cache. When you run an image, Docker adds a thin writable layer on top and starts a single main process.

The key ideas:

Docker in practice for DNS

A minimal example, Pi-hole with Unbound as its upstream (adjust image names, tags and variables to the current documentation before using):

services:
  unbound:
    image: mvance/unbound:latest
    restart: unless-stopped
    networks:
      dns:
        ipv4_address: 172.20.0.2

  pihole:
    image: pihole/pihole:latest
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    environment:
      TZ: "Europe/London"
      FTLCONF_webserver_api_password: "change-me"
      FTLCONF_dns_upstreams: "172.20.0.2#53"
      FTLCONF_dns_listeningMode: "all"
    volumes:
      - ./etc-pihole:/etc/pihole
    depends_on:
      - unbound
    networks:
      dns:
        ipv4_address: 172.20.0.3

networks:
  dns:
    ipam:
      config:
        - subnet: 172.20.0.0/24

Things worth noticing:


LXC: the system container

LXC (Linux Containers) takes the opposite approach. A container is a full userspace: a minimal Debian, Ubuntu, Alpine or similar, with a real init system, its own users, its own package manager, and its own network stack. You start it, log in, and it feels like a small server.

The key ideas:

LXC in practice for DNS

With the classic LXC tools, creating a Debian container, giving it a LAN address through a bridge, and installing Pi-hole looks roughly like this:

# Create a Debian container from the public image server
lxc-create -n dns -t download -- -d debian -r stable -a amd64

# In the container's config (e.g. /var/lib/lxc/dns/config), attach it to your LAN bridge
#   lxc.net.0.type  = veth
#   lxc.net.0.link  = br0
#   lxc.net.0.flags = up

lxc-start -n dns
lxc-attach -n dns

# Inside the container: set a static IP, then install normally
curl -sSL https://install.pi-hole.net | bash

Many people manage LXC through a higher-level tool or web interface that wraps these steps (Proxmox, for example, has its own container tooling). The concepts are the same: the DNS server is a separate, persistent mini-machine with its own address on your network.

Pi-hole's official installer was designed for exactly this kind of environment (a normal Linux system you can install packages into), which is part of why LXC feels natural for it.


Side-by-side comparison

Docker LXC
Model Application container System container (mini machine)
Processes One main process per container Full init system, many services
Filesystem Layered image plus throwaway layer; keep data in volumes Normal persistent root filesystem
Configuration style Declarative (compose file, environment variables) Imperative (commands, config files inside)
Reproducibility Excellent: rebuild from a file Needs scripting or backups to reproduce
Updates Pull a new image, recreate apt upgrade and pihole -up inside
Rollback Pin the previous image tag, restore the volume Snapshot and restore the whole container
Networking default NAT behind the host, ports published Often bridged: own IP on your LAN
Port 53 conflicts Can collide with host services (e.g. systemd-resolved) Own network namespace, no collision
Debugging docker logs, docker exec Normal tools: journalctl, SSH, systemctl
Security isolation Shared kernel, namespaces and capabilities Shared kernel, plus user-namespace separation when unprivileged
Learning curve Concepts to learn (images, volumes, networks) Feels like administering a small server
Best at Repeatable stacks, many small services Long-lived "pet" services that act like machines

Networking: where DNS servers get tricky

A DNS server is unusually sensitive to networking details, because every device on your LAN needs to reach it on port 53, and Pi-hole wants to know which client sent each query. This is where the two models differ most.

Docker networking

With Docker's default bridge network, the container lives on a private subnet behind the host, and you publish ports (53:53) so traffic on the host's IP is forwarded in. Things to watch for:

LXC networking

The most common LXC setup attaches the container to a bridge on your LAN. The container then has its own MAC address and IP, just like a Raspberry Pi would. For DNS this is very convenient:

The usual caveats are those of any bridged setup: Wi-Fi interfaces generally cannot be bridged this way, so the host should be on wired Ethernet, and you should give the container a static or reserved address, since your whole network depends on it.

Encrypted DNS ports

If you serve DoT (port 853) or DoH (port 443) yourself, you need a front end that terminates TLS (a reverse proxy or a DNS-aware proxy) and certificates that renew automatically. Both models can do this. In Docker it is another service in the compose file. In LXC it is another package or a second container. Either way, make sure the certificate renewal path is monitored, because an expired certificate will make encrypted clients fail to connect. Our guide on encrypted DNS on every device covers the client side.


Updates, backups and rollback

Docker

Because the container is disposable, the question is "what must I keep?" The answer is small: your compose file and your volumes.

LXC

The container is a persistent filesystem, so you protect the whole thing.

The honest summary: Docker is better at reproducing a setup, and LXC snapshots are better at undoing a change to a running setup.


Security and isolation

Neither is a security boundary on the level of a virtual machine, because the kernel is shared. A kernel vulnerability that allows container escape can affect both. That said, the details matter.

Docker

LXC

For both models, on a DNS server:

  1. Do not expose port 53 to the internet. An open resolver gets abused for amplification attacks quickly.
  2. Keep the admin interface on your LAN or behind a VPN.
  3. Keep the host patched, since the host kernel protects everything above it.
  4. If you want strong isolation between services, use a VM for that, not a container.

Resources and performance

Both are close to bare metal for CPU and network throughput, and a DNS filter is a light workload anyway. The practical differences are small:

Where performance actually matters for DNS is cache size, upstream latency and disk I/O for the query log, not the container technology. If you are tuning, look at Unbound's cache settings and your log retention first. Our guide on what happens in the 50 ms before every request shows where the time really goes, and the Unbound guide covers tuning.


Which should you choose?

Choose Docker if:

Choose LXC if:

Choose neither (a VM or dedicated hardware) if:

Not sure? For a first Pi-hole on a home network, an LXC container is usually the gentlest start because everything behaves like the guides you will find. Once you are comfortable and want reproducibility, moving to Docker Compose is a reasonable next step. Since your data (blocklists, custom DNS records, allow and deny lists) can be exported with Teleporter, migrating between the two is not painful.


Common mistakes and hybrid setups

Mistakes we see often

  1. No static address. A DNS server whose IP changes silently breaks the whole network. Reserve it in your router or set it statically.
  2. One DNS server, no fallback. If it is down, "the internet is broken" for everyone. Consider a second instance, or at least a sensible fallback for non-critical devices.
  3. Using latest tags in Docker and being surprised by a major upgrade.
  4. No snapshot before an LXC upgrade, then no way back.
  5. Forgetting persistent storage in Docker, so recreating the container wipes your configuration.
  6. Publishing port 53 on a public interface and creating an open resolver.
  7. Running a privileged LXC container because a tutorial said to, without needing it.
  8. Letting certificates expire on an encrypted DNS endpoint.

Hybrid approaches


FAQ

Is LXC the same as Docker? No. They share the same kernel features, but Docker is designed to package and run single applications, while LXC is designed to run a whole lightweight operating system.

Can I run Pi-hole in either one? Yes. Pi-hole publishes an official Docker image, and its installer works on standard Linux systems, which includes an LXC container.

Which is more secure? Neither wins outright. An unprivileged LXC container and a rootless Docker setup are both reasonable. What matters more is not exposing port 53 publicly, keeping the host patched, and giving each container only the permissions it needs.

Which uses less memory? Docker typically has a slightly smaller footprint per service. For a DNS filter, the difference is not usually important.

Which is easier to back up? LXC, if your storage supports snapshots: the whole machine is one unit. Docker is easy too, as long as you remember to back up your volumes and compose file.

Do I need a VM instead? Only if you want stronger isolation than a shared kernel gives you, or you need to run a different operating system.

Can I switch later? Yes. Export your settings with Pi-hole's Teleporter and your Unbound configuration file, then import them into the new environment. Plan a short maintenance window, and keep the old instance until the new one has proven itself.


Further reading

Skip the setup

Get your own private DNS Node with Unbound + AdGuard Home / Pi-hole with DoH and DoT endpoints.