Short answer: you cannot. Docker has no supported way to add a published port to a container that is already running. Port mappings are fixed at container creation, and neither docker run nor docker container update can change them afterwards. The --publish-add flag exists only for Swarm services, not standalone containers.
If you cannot restart the container, these are the options that actually work:
| Approach | Adds a real Docker port mapping? | Works on Docker Desktop (macOS/Windows)? | Survives restart? |
|---|---|---|---|
socat sidecar container | No (TCP forward) | Yes | Yes, if the sidecar restarts |
| Reverse proxy (Nginx/Traefik/HAProxy) | No (proxied) | Yes | Yes |
Host iptables DNAT | No (host NAT rule) | No, native Linux only | No, unless persisted |
Recreate the container with -p | Yes | Yes | Yes |
The only approach that produces a genuine Docker-managed published port — one that shows up in docker ps and docker port — is recreating the container. Everything else forwards traffic at a layer above or below Docker’s port bookkeeping. Pick based on whether you can tolerate a restart, and read the caveats below before running anything in production.
This article explains each method, the exact commands, and where each one breaks.
Background: how Docker port mapping works
Fundamental principles of container port mapping
In Docker, the connection between a container’s internal port and the host machine’s port is facilitated through port mapping. Usually, we specify port mappings using the -p or —publish parameters when starting a container, as illustrated below:
docker run -d -p 8080:80 nginx
The command above maps port 8080 on the host machine to port 80 inside the container. As a result, external users can access the web service running within the container through port 8080 on the host.
Why Docker does not allow this
Once a container has started, Docker generally doesn’t support adding new port mappings dynamically. In other words, the initial port mappings remain fixed throughout the container’s lifecycle. If you need to add more port mappings, the traditional approach involves stopping and restarting the container, which can disrupt services and is unacceptable in production environments.
The four workarounds
To dynamically add port mappings to a running container, several methods can be employed:
2.1 Sidecar container that forwards the port (recommended)
A separate container publishes the new host port and forwards traffic to the original container over a shared Docker network. This is the safest option because the original container is never touched.
An important correction first: you cannot combine --network container:<name> with -p. Docker’s networking documentation states that --publish, --publish-all, and --expose are not supported for containers using container: network mode, because such a container has no network namespace of its own to map ports into. Any guide that tells you to run docker run -p 8081:81 --net container:your-container ... is wrong, and Docker will reject it.
The working pattern uses a user-defined network so the sidecar can reach the target by container name:
# 1. Create a network and attach the running container to it (no restart needed)
docker network create app-net
docker network connect app-net your-container
# 2. Start a socat sidecar that publishes 8081 and forwards to the target's port 81
docker run -d --name port-sidecar \
--network app-net \
--restart unless-stopped \
-p 8081:81 \
alpine/socat \
TCP-LISTEN:81,fork,reuseaddr TCP:your-container:81
Note that docker network connect works on a running container, so step 1 causes no downtime. The sidecar listens on port 81 inside its own namespace, and -p 8081:81 publishes that to the host.
Caveats:
- This is a TCP forward, not a Docker port mapping. It will not appear in
docker port your-container. alpine/socatforwards TCP only. For UDP useUDP-LISTEN/UDP, and for HTTP with host-based routing prefer Nginx, Traefik, Caddy, or HAProxy.- Add
--restart unless-stopped(as above) or the forward disappears on reboot. - The extra hop costs a small amount of latency and adds one more process to monitor.
2.2 Host iptables DNAT rule (native Linux only)
On a native Linux host you can add a DNAT rule that forwards a host port to the container’s internal IP:
# Get the container IP
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' your-container)
# Forward host port 8081 to container port 81
sudo iptables -t nat -A DOCKER -p tcp --dport 8081 \
-j DNAT --to-destination "${CONTAINER_IP}:81"
This gives you fine-grained control but carries the most operational risk of any method here:
- Docker Desktop on macOS and Windows will not work this way. Containers run inside a Linux VM, so
iptablesrules on your machine do not touch Docker’s networking path. This method is native Linux only. - Rules do not persist. They are lost on reboot, firewall reload, or an nftables/iptables transition. Use your distribution’s firewall persistence mechanism if you need them to survive.
- Docker owns the
DOCKERchain. Docker creates and manages these rules from the port configuration of running containers, and its documentation says you should not modify the rules Docker creates. For custom filtering, Docker designatesDOCKER-USERas the placeholder for user-defined rules, because rules appended toFORWARDare processed after Docker’s own. - Container IPs are not stable. The address changes whenever the container is recreated, leaving a stale rule that silently forwards nowhere.
- Bypasses Docker’s bookkeeping. The port will not appear in
docker psordocker port.
2.3 Running socat directly on the host
You can also run socat as a plain host process instead of in a container:
socat TCP-LISTEN:8081,fork,reuseaddr TCP:<container_ip>:81
This works on native Linux, where the container IP is routable from the host. On Docker Desktop for macOS and Windows the container IP is not reachable from your machine, so use the sidecar in section 2.1 instead. Either way you need a process supervisor (systemd, or --restart on the sidecar) to survive reboots, since a bare socat process dies with its shell.
2.4 Recreate the service with Docker Compose
This is the only method that yields a real, Docker-managed published port. Add the mapping to compose.yaml:
services:
app:
image: your-image:tag
ports:
- "8081:81"
Then recreate just that service:
docker compose up -d app
Compose recreates the container, so there is a brief interruption — this is not a live change. Note that modern Docker uses docker compose (a subcommand), not the older standalone docker-compose binary. Keep state in named volumes or bind mounts so it survives recreation.
2.5 Editing Docker’s internal config files (not recommended)
You will find advice to hand-edit /var/lib/docker/containers/<id>/config.v2.json and hostconfig.json to add a PortBindings entry, then restart the daemon. It sometimes works, but treat it as a last resort:
- These are internal implementation files with no stability guarantees, not a supported API. The format can change between Docker releases.
- The daemon holds container state in memory. Editing files under a running daemon risks having your changes overwritten, and partial edits can leave the container’s network state inconsistent with its metadata.
- You must stop the daemon before editing, which affects every container on the host.
- If
live-restoreis enabled, containers keep running across a daemon restart — but that does not apply an edited port mapping. Live-restore does not change the rule that a new published port requires container recreation.
If you have reached the point of editing daemon state by hand, recreating the container with the correct -p flag is faster and safer.
Conclusion
Docker does not support adding a published port to a running container, and no workaround changes that. What the methods above give you is a way to route new traffic to a container you cannot restart.
Choose in this order:
- Can you tolerate a short restart? Recreate the container with the right
-pflag, or addports:tocompose.yamland rundocker compose up -d. This is the only method that produces a real Docker port mapping. - Cannot restart? Use the
socatsidecar in section 2.1, or a reverse proxy if you need HTTP routing, TLS, or health checks. - Native Linux and need a quick temporary forward? An
iptablesDNAT rule works, but persist it deliberately and expect Docker to interfere with its own chains. - Avoid hand-editing daemon config files.
If port mappings change often, that is usually a design signal: put a reverse proxy in front of the service from the start, and let it own the host-facing ports so container lifecycle and routing stay independent.
Sources: Docker: Publishing ports, Docker: Container networking modes, Docker: Packet filtering and firewalls, Docker and iptables, docker container port.
You can visit Novita AI for GPU instances and model APIs.
