# UFW Docker compatibility rules # Append these to /etc/ufw/after.rules (after the existing COMMIT) # Blocks all external access to Docker-published ports except: # - Trusted IPs (add your own) # - Explicitly allowed public ports (80, 443, 22) # - Docker internal and loopback traffic # # This chain is the ONLY thing gating Docker-published ports from the internet: # Docker writes its own DNAT/FORWARD rules and bypasses ufw entirely, so a # `ufw allow ` has no effect on a published container port. If a container # port must be reachable publicly, it needs a RETURN line below. # # Failure mode to recognise: the port appears open locally and `docker ps` shows # it published, but external clients hang at TCP connect with no refusal. Local # tests pass because they arrive via `lo` and match the loopback RETURN above, # never reaching the DROP. Test from another machine, not from the host itself. *filter :DOCKER-USER - [0:0] # Allow established/related -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN # Allow loopback -A DOCKER-USER -i lo -j RETURN # Allow Docker internal networks -A DOCKER-USER -s 172.16.0.0/12 -j RETURN # Allow trusted external sources (add more lines as needed) # -A DOCKER-USER -s -j RETURN # Allow public ports -A DOCKER-USER -i eth0 -p tcp --dport 80 -j RETURN -A DOCKER-USER -i eth0 -p tcp --dport 443 -j RETURN # gitea SSH (NPM stream -> gitea:22). Host sshd does not use :22 publicly on a # box that serves gitea — it moves to :2022 plus the tailnet IP, see ssh.socket. -A DOCKER-USER -i eth0 -p tcp --dport 22 -j RETURN # Drop everything else from external -A DOCKER-USER -i eth0 -j DROP # Return for non-external traffic -A DOCKER-USER -j RETURN COMMIT