Docker Security: The Lost Guide for Developers
Learn how to protect your network from threats and dangerous configuration!

Table of Contents
- ⚠️ Local Networks at Risk
- 🛡️ Firewall Configuration
- 🔐 Secrets Management for Local Development
- 🕵️ Credential Leaks and Side-Channel Attacks
- 🔍 Monitoring & Canary Tokens
- ❌ Common Misconceptions
⚠️ Local Networks at Risk
Let’s be honest, we’ve all done it. You’ve connected to a random coffee shop Wi-Fi or let someone use your home network without a second thought. Maybe you even trust your smart fridge not to compromise your network. The reality? These casual decisions can expose your local development setup to unnecessary risks. Attackers don’t just target production systems—local environments are often softer targets, offering a way to access sensitive projects.
Attack Scenarios
- Intercepted Traffic: Unencrypted traffic can easily be captured and read.
- Unprotected Services: Local databases or APIs exposed on
0.0.0.0
. - Network Spoofing: Redirects traffic to an attacker’s device.
Quick Fixes
- Prefer private docker networks over firewalls to limit network exposure.
- Avoid public or shared Wi-Fi; prefer using your phone’s hotspot.
- Monitor your local network for unknown devices using tools like
arp-scan
andnmap
.
🛡️ Firewall Configuration
UFW with Docker (Ubuntu)
⚠️ Warning: By default Docker on Ubuntu/Debian will bypass UFW/iptables rules, potentially exposing your system to attacks. It doesn’t matter if you bind ports to local IP addresses (e.g.
-p 127.0.0.1:8080:80
.)
This surprises me every time I learn about it! Docker bypasses UFW rules by default, allowing containers to communicate with the host and other containers without restriction.
Best Practice
- 🥇 Use Docker Networks to isolate and control what can connect to each container or network.
- 🥉 Update iptables if you must use a
host
network, or cannot use custom networks, you can mitigate the risk by configuring iptables. Not for the faint-hearted, check out utility below.
Docker Network Isolation
# Create a new Docker networkdocker network create my-network
# Run your container with the new networkdocker run --network my-network my-container
UFW Configuration (for host
networks)
There’s a lot of bad advice on fixing this out there. configure UFW to work with Docker using UFW largely like you might expect.
I’ve used ufw-docker
to configure a self hosted system and it seems to works well.
# Install binary as root (needs root permissions anyway)sudo wget -O /usr/local/bin/ufw-docker \ https://github.com/chaifeng/ufw-docker/raw/master/ufw-dockersudo chmod +x /usr/local/bin/ufw-docker# Install and modify the `after.rules` file of `ufw`ufw-docker install
ufw-docker help
This command performs the following:
- Backs up the file
/etc/ufw/after.rules
. - Appends Docker-related rules at the end of the file to integrate properly with UFW.
Source: ufw-docker GitHub
Example Usage:
# Allow Docker container on port 8080ufw-docker allow <container_name> 8080/tcp
# Manage rules safely alongside your UFW configurationufw-docker status
Note: Most “fixes” for Docker-UFW conflicts involve manual iptables rules, which can be error-prone and fragile during updates.
macOS Firewall
- Go to System Preferences > Security & Privacy > Firewall.
- Enable the firewall and click “Firewall Options.”
- Block all incoming connections except essential services.
Note: You may need to lookup configuration for your firewall to allow certain smart devices you use - e.g. Google Cast/AirPlay and other services.
Commands for Advanced Users (macOS and Linux)
macOS:
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setblockall on # Block allsudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /path/to/app # Allow specific app
Linux (ufw):
ufw default deny incoming # Block all incomingufw allow ssh # Allow SSH# allow 443 and 80 for web trafficufw allow 80/tcpufw allow 443/tcpufw enable # Enable firewall
Pro Tip: Use tools like Little Snitch on macOS and ufw on Linux for more user-friendly configurations.
🔐 Secrets Management for Local Development
Proactive Placeholder Validation
💡 Ensure secrets are properly setup with real values before running your application.
If you use placeholders like __WARNING_REPLACE_ME__
in your secrets, greaet, maybe someone will notice. Just in case, you can also add a little validation to provide safety at runtime.
You wouldn’t believe how easy it is to completely hack (modify & re-sign) a JWT token when attackers can guess the secret!
const validateSecrets = () => { const unsafePlaceholder = /__WARNING_REPLACE_ME__/; const missingSecrets = Object.entries(process.env).filter( ([key, value]) => unsafePlaceholder.test(value) );
if (missingSecrets.length) { console.error("Unsafe secrets detected:", missingSecrets); process.exit(1); }};
validateSecrets();
use std::env;
fn validate_secrets() { let unsafe_placeholder = "__WARNING_REPLACE_ME__"; for (key, value) in env::vars() { if value.contains(unsafe_placeholder) { panic!("Unsafe secret in {}", key); } }}
fn main() { validate_secrets();}
package main
import ( "fmt" "os" "strings")
func validateSecrets() { placeholder := "__WARNING_REPLACE_ME__" for _, env := range os.Environ() { pair := strings.SplitN(env, "=", 2) if len(pair) == 2 && strings.Contains(pair[1], placeholder) { panic(fmt.Sprintf("Unsafe secret in %s", pair[0])) } }}
func main() { validateSecrets()}
Generating and Storing Secrets
Never hardcode secrets in your codebase. Prefer environment variables and secure vaults.
Instead of .env.example
, use .env.generate.sh
to make it easy for users to get a .env
file with secure “defaults.”
Example .env.generate.sh
#!/bin/bash# Generates a secure .env file for local development
generate_secret() { local length=${1:-30} # add 4 bytes to account for padding local generate_length=$((length + 4)) openssl rand -base64 "$generate_length" | tr -d '+=/\n' | cut -c1-"$length"}# Bail out if .env file already exists[ -f .env ] && { echo ".env file already exists!"; exit 1; }
cat <<EOL > .env# Database settings & secretsDB_USER=app_userDB_PASSWORD=$(generate_secret 30)REDIS_PASSWORD=$(generate_secret 20)# Session secretsSESSION_KEY=$(generate_secret 32)JWT_SECRET=$(generate_secret 64)EOL
echo "New .env file generated!"
🕵️ Monitoring & Double-checking
nmap
Examples
Testing Inside Your Network
# Scan your localhost for all open portsnmap -sT localhost
# Scan your machine’s private IP for servicesnmap -sV 192.168.1.10
# Detect devices on your networknmap -sn 192.168.0.0/24nmap -sn 10.0.0.0/24
Testing Outside Your Network
To can lookup your current (public) IP easily with services like ifconfig.me
: curl https://ifconfig.me
.
Use an external network or remote server to test your public IPs:
print_current_ip() { curl https://ifconfig.me}
print_current_ip# --> 123.456.789.012
# Change target_host to your public ip or hostname# Check host using advanced techniquesnmap -A --open --reason $target_hostnmap -A -F --open --reason $target_hostnmap -A -p1-65535 --open --reason $target_host
Why Test Both? Testing from inside reveals internal exposure, while external tests identify services accessible to attackers.
🛡️ Common Misconceptions
- My local environment isn’t a target.
- Fact: Attackers can pivot from your machine to your production systems.
- Firewalls block everything.
- Fact: They only block what you configure them to.
- Private IPs are secure.
- Fact: Exploits like NAT bypasses can still affect your network.