Need to run containers locally but control exactly which external domains they can reach?
Docker’s internal networks block all internet access — and by adding a L7 Squid proxy as the only gateway out, you get clean domain-level allowlisting.
The Concept
┌────────────────────────────────────┐
│ internal network │
│ │
│ ┌──────┐ ┌───────┐ │
│ │ app1 │─────────►│ │ │
│ └──────┘ │ squid │─────────► internet (allowed domains only)
│ ┌──────┐ │ │ │
│ │ app2 │─────────►│ │ │
│ └──────┘ └───────┘ │
│ │
└────────────────────────────────────┘
app1andapp2sit on an internal network — no direct route to the internet.squidsits on both the internal network and a normal network, acting as the only door out.- Squid’s config decides which domains are allowed. Everything else is denied.
Docker Compose Setup
mkdir ~/demo && cd ~/demo
#~/demo
# ├── docker-compose.yml
# └── squid.conf
squid.conf
cat <<'EOF' > squid.conf
# Allowlisted domains — edit this list to suit your needs
acl allowed_domains dstdomain .github.com
acl allowed_domains dstdomain .pypi.org
acl allowed_domains dstdomain .google.com
# Standard ports
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
# Allow CONNECT for HTTPS
acl CONNECT method CONNECT
# Access rules
http_access allow CONNECT SSL_ports allowed_domains
http_access allow allowed_domains
http_access deny all
# Listener
http_port 3128
EOF
Edit the acl allowed_domains lines for your needs.
docker-compose.yml
You may take this architecture and apply it to any existing compose file, just add the squid service and update the networks. Here’s a complete demo for reference:
cat <<'EOF' > docker-compose.yml
services:
squid:
image: ubuntu/squid
container_name: squid
networks:
- internal
- external
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
app1:
image: curlimages/curl
container_name: app1
entrypoint: ["sleep", "3600"]
environment:
- http_proxy=http://squid:3128
- https_proxy=http://squid:3128
networks:
- internal
depends_on:
- squid
app2:
image: curlimages/curl
container_name: app2
entrypoint: ["sleep", "3600"]
environment:
- http_proxy=http://squid:3128
- https_proxy=http://squid:3128
networks:
- internal
depends_on:
- squid
networks:
internal:
internal: true # no internet access for containers on this network
external:
driver: bridge # normal network — squid uses this to reach the internet
EOF
Key details: the internal: true network has no route out. The app containers only connect to that network. Squid connects to both, so it’s the sole gateway — and it only forwards requests to domains you’ve explicitly allowed.
Start the continers
docker compose up -d
Validate Everything (All-in-one check)
echo "=== Allowed domain ===" && \
STATUS=$(docker exec app1 curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://www.google.com) && \
[ "$STATUS" = "200" ] && echo "✅ PASS ($STATUS)" || echo "❌ FAIL ($STATUS)" && \
echo "=== Blocked domain ===" && \
STATUS=$(docker exec app1 curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://example.com) && \
[ "$STATUS" = "403" ] && echo "✅ BLOCKED (good - $STATUS)" || echo "❌ FAIL ($STATUS)" && \
echo "=== Direct internet ===" && \
docker exec app1 curl -sS --noproxy '*' --connect-timeout 3 http://8.8.8.8 2>&1 && echo "❌ FAIL (leaked)" || echo "✅ BLOCKED (good)" && \
echo "=== Container-to-container ===" && \
docker exec app1 curl -sS --noproxy '*' --connect-timeout 3 http://app2:12345 2>&1 | grep -q "Failed to connect" && echo "✅ PASS (reachable)" || echo "❌ FAIL"
Adding or Removing Domains
Edit squid.conf and restart:
# Add a new domain to squid.conf, then:
docker compose restart squid
Cleanup
docker compose down