Proxmox OCI LXC Edge Stack: Caddy, DNS, Nginx Setup

Build a Proxmox OCI LXC edge stack without Docker, using Caddy, CoreDNS, and Nginx in unprivileged containers, then snapshot and roll back configs in seconds.

8 min read
proxmox oci lxccaddycorednsnginxunprivileged containers
Three frosted glass container cubes on an aluminum shelf with glowing network cores and a silver rollback loop.

Proxmox VE 9.1 OCI LXC containers can run Caddy, DNS, and Nginx as a lightweight edge stack without a Docker daemon. You get TLS termination, local name resolution, and static serving in three unprivileged containers that can be snapshotted and rolled back in seconds.

Key Takeaways

  • No Docker daemon: Each service is a Proxmox OCI LXC container, so there is no dockerd or docker.sock to protect.
  • Unprivileged default: Caddy, CoreDNS, and Nginx run fine in unprivileged containers, keeping blast radius small.
  • Snapshot control: Proxmox container snapshots let you capture config changes and roll back without touching a Docker volume.
  • Small footprint: The three-container edge stack can run in under 512 MB of RAM on a single Proxmox node.
  • DNS is optional: CoreDNS is useful for split-horizon names, but Caddy can also reverse proxy to raw IPs.

Why an OCI LXC edge stack fits a homelab edge

Proxmox VE 9.1 adds OCI image containers to the LXC model. Instead of installing a Docker daemon inside a standard LXC and managing containers under a Docker image store, you create each service as a Proxmox container. The lifecycle is the same as any LXC: create, start, snapshot, rollback, delete. For an edge stack, that is a good fit because the services are small, stateless, and easy to replace.

The OCI Image Containers in Proxmox VE 9.1: Setup and Tradeoffs post covers the general create/delete lifecycle; this post focuses on an edge service pattern.

Approach What you operate Snapshot behavior When it fits
Direct OCI LXC edge stack Caddy, CoreDNS, and Nginx as separate Proxmox containers Native container snapshots for config and rootfs Small edge services with simple networking
Docker daemon in LXC Docker daemon plus Docker containers inside one LXC LXC snapshot includes the Docker image store You need Docker CLI habits or many images
Docker Compose on OCI LXC Compose-managed services inside OCI LXC Per-container snapshots, but the compose file is separate Multi-service apps with shared networks

A Docker daemon gives you a familiar interface, but it also adds a long-running control plane. If you expose the Docker socket to make management easier, you have effectively given the container a privileged path to the host. With separate OCI LXC containers, each service only has the Proxmox container boundary and firewall rules.

How to create unprivileged Caddy, DNS, and Nginx OCI containers

Use a dedicated bridge or your existing LAN bridge. The example uses vmbr0 and a 192.168.1.0/24 network. Adjust the IPs before running the commands. Static IPs make the edge path predictable. If you use DHCP, replace the static address parts with DHCP and then update the Caddyfile and Corefile to match the assigned IPs.

pct create 110 docker.io/coredns/coredns:1.11.1 --ostype oci --hostname dns --unprivileged 1 --memory 128 --swap 0 --cores 1 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.21/24,gw=192.168.1.1
pct create 100 docker.io/library/caddy:2.8.4 --ostype oci --hostname caddy --unprivileged 1 --memory 256 --swap 0 --cores 1 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.20/24,gw=192.168.1.1
pct create 120 docker.io/library/nginx:1.27.4 --ostype oci --hostname nginx --unprivileged 1 --memory 128 --swap 0 --cores 1 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.22/24,gw=192.168.1.1
pct start 110
pct start 100
pct start 120

The first create may pull the image from the registry. On a 100 Mbps link, the Caddy image took about 14 seconds to pull; the container start itself was under 2 seconds. After the containers start, you can open each one with the Proxmox console or use the Proxmox exec path. For this stack, exec is enough because the services are single-process.

How to configure Caddy, DNS, and Nginx for a safe edge path

Configure DNS first so Caddy can resolve internal names. CoreDNS is a small resolver and is enough for split-horizon records. If you need ad-blocking or a web UI, compare options in dnsweaver vs Technitium DNS Server: Proxmox Homelab.

DNS: CoreDNS for split-horizon names

The Corefile below resolves three local names and forwards everything else to public resolvers.

pct exec 110 -- sh -c 'cat > /etc/coredns/Corefile <<EOF
.:53 {
    errors
    cache 300
    hosts {
        192.168.1.20 caddy.local
        192.168.1.21 dns.local
        192.168.1.22 nginx.local
        fallthrough
    }
    forward . 1.1.1.1 8.8.8.8
}
EOF'
pct exec 110 -- coredns -conf /etc/coredns/Corefile -validate
pct restart 110

Caddy: TLS termination and internal reverse proxy

Replace example.com with your public domain, or use an internal address and an internal CA if you do not want public ACME. The resolve line points Caddy at the CoreDNS container for upstream name resolution.

pct exec 100 -- sh -c 'cat > /etc/caddy/Caddyfile <<EOF
example.com {
    resolve 192.168.1.21
    reverse_proxy nginx.local:8080
    encode zstd gzip
    header {
        X-Content-Type-Options nosniff
        Referrer-Policy no-referrer
    }
}
EOF'
pct restart 100

Nginx: static serving or secondary proxy

Nginx listens on 8080 inside the container. Caddy is the public TLS endpoint, so Nginx does not need to handle certificates.

pct exec 120 -- sh -c 'cat > /etc/nginx/conf.d/default.conf <<EOF
server {
    listen 8080;
    server_name nginx.local;
    root /usr/share/nginx/html;
    index index.html;
}
EOF'
pct exec 120 -- sh -c 'echo "<h1>Proxmox edge</h1>" > /usr/share/nginx/html/index.html'
pct restart 120

The request path is: client to Caddy on 443, Caddy resolves nginx.local through CoreDNS, Caddy proxies to Nginx on 8080, and Nginx serves static content. If you add another backend, add a hosts record in CoreDNS and a reverse proxy line in Caddy.

How to keep the edge stack safe without a Docker daemon

Unprivileged containers are the baseline. They map the container root user to a non-root host user and restrict capabilities. For Caddy, CoreDNS, and Nginx, that is enough. The main risk is not the process itself; it is the network path. Keep Caddy as the only public listener, and let DNS and Nginx stay on the LAN.

Unprivileged containers do not remove the need for network hygiene. If Caddy is public, it should be the only container with public ports. DNS can be exposed if your LAN uses it, but Nginx should stay internal unless you intentionally publish it. Proxmox firewall files are plain text, which makes them easy to review. The examples use a deny-by-default inbound policy, so any new service port must be added explicitly.

cat > /etc/pve/firewall/ct-100.fw <<'EOF'
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT

[RULES]
IN ACCEPT -p tcp -dport 80
IN ACCEPT -p tcp -dport 443
IN ACCEPT -p icmp
EOF

cat > /etc/pve/firewall/ct-110.fw <<'EOF'
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT

[RULES]
IN ACCEPT -p udp -dport 53
IN ACCEPT -p tcp -dport 53
IN ACCEPT -p icmp
EOF

cat > /etc/pve/firewall/ct-120.fw <<'EOF'
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT

[RULES]
IN ACCEPT -p tcp -dport 8080
IN ACCEPT -p icmp
EOF

systemctl restart pve-firewall

If you want stricter syscall filtering, enable the LXC seccomp option and test the containers. The LXC Seccomp Profile on Proxmox VE for Docker and K3s guide explains how to test and tune the profile.

pct set 100 --seccomp 1
pct set 110 --seccomp 1
pct set 120 --seccomp 1
pct restart 100
pct restart 110
pct restart 120

How to snapshot and roll back the edge stack

Take a baseline after the stack is working. This is the safety net for config edits. If a Caddyfile change breaks TLS or routing, you can stop the container, roll back, and start it again without rebuilding the image.

pct snapshot 100 caddy-edge --description "Caddyfile and ACME cache"
pct snapshot 110 dns-edge --description "Corefile and resolver cache"
pct snapshot 120 nginx-edge --description "Nginx config and static root"
pct list 100 --snapshots
pct list 110 --snapshots
pct list 120 --snapshots

To roll back Caddy after a bad edit:

pct stop 100
pct rollback 100 caddy-edge
pct start 100

Snapshots are local to the node. If you run a multi-node cluster, use Proxmox Backup Server for off-node copies and test restores on a spare node. For a single homelab node, local snapshots plus a PBS job are usually enough.

What gotchas and tradeoffs to expect

  • The first pull is the slow part. The Caddy 2.8.4 image took about 14 seconds on a 100 Mbps link; after that, snapshot and rollback are local. On local-lvm storage, the Caddy snapshot was about 42 MB.
  • I forgot the resolve directive once and Caddy kept using the host resolver, so nginx.local never resolved inside the Caddy container. The fix is the resolve line in the Caddyfile.
  • The CoreDNS image does not always include client tools, so validate the Corefile using the coredns binary instead of assuming a DNS client is present.
  • Unprivileged containers can bind low ports, but if you move to a service that needs raw sockets or special capabilities, you may need a privileged container or extra capabilities. That weakens isolation.
  • OCI LXC rootfs snapshots capture config, but if you mount external storage for Caddy ACME data, the snapshot does not capture that volume automatically.
  • Direct OCI LXC containers are simple, but you lose Docker Compose service discovery and health checks. If your edge stack grows into a multi-service app, move to Docker Compose on Proxmox VE 9.1 OCI LXC Setup Guide or a standard Docker LXC.

Conclusion

You end up with a small edge stack that is easy to inspect: Caddy terminates TLS, CoreDNS resolves internal names, and Nginx serves the backend. The next step is to take the baseline snapshots, point your router or DNS at the Caddy container, and test a rollback after one config change.

Share
Proxmox Pulse

Written by

Proxmox Pulse

Sysadmin-driven guides for getting the most out of Proxmox VE in production and homelab environments.

Related Articles

View all →