Skip to content

Instantly share code, notes, and snippets.

@torgeir
Last active June 16, 2026 06:38
Show Gist options
  • Select an option

  • Save torgeir/2a5d94b76b4a2a9debcdb53ea0e34a2e to your computer and use it in GitHub Desktop.

Select an option

Save torgeir/2a5d94b76b4a2a9debcdb53ea0e34a2e to your computer and use it in GitHub Desktop.
Domain-allowlist network sandbox for docker compose (nftables + dnsmasq sidecar)
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
curl \
dnsutils \
iputils-ping \
netcat-openbsd \
&& rm -rf /var/lib/apt/lists/*
no-resolv
no-hosts
listen-address=127.0.0.1
bind-interfaces
cache-size=500
# log all queries
log-queries
log-facility=- # "-" = stderr -> docker logs
# strip AAAA answers — the nft set is v4-only, so without this curl
# may try an (unfiltered, blocked) IPv6 address first and stall
filter-AAAA
# example allow dns and connections
#server=/nrk.no/1.1.1.1
# format: 4#<table family>#<table>#<set>
#nftset=/nrk.no/4#inet#sandbox#allowed
# usage:
# docker compose down
# docker compose build firewall
# docker compose up -d --remove-orphans
# docker compose run app bash
# see logs:
# docker compose logs firewall -f
# works:
# dig nrk.no
# curl https://www.nrk.no
# fails:
# dig google.no
# curl https://www.google.no # fails
# curl http://www.nrk.no # fails
services:
firewall:
build:
context: .
dockerfile: ./firewall.Dockerfile
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD-SHELL", "nft list table inet sandbox >/dev/null && getent hosts localhost >/dev/null"]
interval: 1s
timeout: 2s
retries: 30
start_period: 5s # grace period before failures count
#start_interval: 1s # fast polling during startup only (compose 2.22+)
app:
build:
context: .
dockerfile: ./app.Dockerfile
network_mode: "service:firewall"
depends_on:
firewall:
condition: service_healthy
#!/bin/bash
set -e
nft delete table inet sandbox 2>/dev/null || true
nft -f /etc/nftables-sandbox.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf
# allow list
for d in nrk.no \
aftenposten.no \
pypi.org \
files.pythonhosted.org \
registry.npmjs.org; do
echo "server=/$d/1.1.1.1"
echo "nftset=/$d/4#inet#sandbox#allowed"
done >> /etc/dnsmasq-sandbox.conf
# -k = keep in foreground; exec makes dnsmasq PID 1 so the
# container lives exactly as long as the resolver does.
exec dnsmasq -k --conf-file=/etc/dnsmasq-sandbox.conf --user=dnsmasq
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends nftables dnsmasq \
&& rm -rf /var/lib/apt/lists/*
COPY nftables.conf /etc/nftables-sandbox.conf
COPY dnsmasq.conf /etc/dnsmasq-sandbox.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
table inet sandbox {
set allowed {
type ipv4_addr
}
chain input {
type filter hook input priority 0; policy drop;
iifname lo accept
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy drop;
ip daddr 127.0.0.11 drop
oifname lo accept
ct state established,related accept
meta skuid "dnsmasq" ip daddr 1.1.1.1 udp dport 53 accept
meta skuid "dnsmasq" ip daddr 1.1.1.1 tcp dport 53 accept
# HTTP(S) to IPs that dnsmasq resolved for allowlisted domains
ip daddr @allowed tcp dport { 443 } accept
reject with tcp reset
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment