-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Problem
Dokploy allows exposing ports for applications and databases, but there is no way to restrict access by source IP. This is a common security need — for example, restricting database ports to office IPs only.
Docker Swarm makes this notoriously difficult:
- Docker bypasses host firewalls — published ports go through the
FORWARDchain, soufw/firewalldrules are ignored (Docker docs) - Swarm ingress SNATs traffic — in
ingressmode, real client IPs are replaced with internal swarm IPs (10.255.0.x), making IP-based filtering impossible at the application/proxy level (moby/moby #25526) - No built-in solution — Docker provides no native per-port IP filtering
Proposed Solution
Add per-port IP allow-list filtering using iptables DOCKER-USER chain, managed entirely by Dokploy. This works at the kernel level with zero overhead (no proxy, no sidecar).
How it would work
- New
ip_allow_listtable: each port (application or database) can have multiple allowed CIDRs - Dokploy generates iptables rules in a custom
DOKPLOY-FILTERchain (sub-chain ofDOCKER-USER) - Rules are applied locally via
execAsyncand on remote swarm nodes via SSH - Empty allow-list = port open to all (backward-compatible)
- Changes apply immediately — no redeploy needed
Example rules
# Host mode — straightforward
iptables -A DOKPLOY-FILTER -p tcp --dport 5432 -s 10.0.0.0/8 -j RETURN
iptables -A DOKPLOY-FILTER -p tcp --dport 5432 -j DROP
# Ingress mode — needs conntrack for port matching after DNAT
iptables -A DOKPLOY-FILTER -p tcp -m conntrack --ctorigdstport 8080 -s 1.2.3.0/24 -j RETURN
iptables -A DOKPLOY-FILTER -p tcp -m conntrack --ctorigdstport 8080 -j DROPKey Design Decisions — Request for Comments
I'd like community input on these decisions before implementation:
1. Ingress mode: is IP filtering even possible?
In Docker Swarm ingress mode, source IPs are SNATed to internal addresses before packets reach the DOCKER-USER chain. This means -s <real-client-ip> won't match.
Options:
- A) Only support IP filtering in
hostmode. Auto-switch ports tohostwhen IP filtering is enabled (loses routing mesh load balancing). - B) Support both modes but warn users that ingress mode IP filtering relies on
conntrackand may not preserve real source IPs on all setups. - C) Use the docker-ingress-routing-daemon approach to recover real IPs in ingress mode (adds complexity).
This is the biggest open question. If SNAT makes ingress-mode filtering impossible, the feature should either force host mode or clearly document the limitation.
2. Scope: which resources should support IP filtering?
- Applications (per-port via existing
portstable) — seems obvious - Databases (Postgres, MySQL, MariaDB, MongoDB, Redis via
externalPort) — arguably the highest-value target - Compose services — ports are user-defined in compose files, much harder to intercept
Current thinking: Applications + Databases. Compose out of scope. Thoughts?
3. Data model for database allow-lists
Application ports have a clean FK (portId). Databases have externalPort as a direct integer field on 5 separate schemas. Options:
- A) Polymorphic
databaseId+databaseTypecolumns (simple but no FK constraint, risk of orphaned rows) - B) 5 explicit nullable FK columns (
postgresId,mysqlId, etc.) with cascade delete (safe but verbose) - C) Add a unified
portstable for databases too, then just useportIdeverywhere (cleanest but bigger refactor)
4. Rule persistence without external services
Current plan: Dokploy applies rules on startup, on CRUD changes, on deploy, and via a periodic reconciliation interval (every ~5 minutes). No systemd services or scripts on disk — all state lives in the DB.
Concerns:
- Brief unprotected window during flush-and-rebuild. Should we use
iptables-restorefor atomic swaps? - Race conditions between concurrent applies. Need a mutex?
- If Dokploy crashes, DROP rules persist (could lock users out). Should there be a cleanup on shutdown?
5. IPv6 support
The iptables approach is IPv4-only. Should we also generate ip6tables rules, or declare IPv6 out of scope initially?
6. Allow-list only, or also deny-list?
Current plan: allow-list only (empty = open to all). This follows least-privilege. Is deny-list support needed?
7. UI placement
Current plan: a "shield" icon on each port row that opens an IP allow-list dialog. Databases get the same UI when externalPort is set. Any UX preferences?