← Start of tutorial

WIUNIX ACADEMY

Why is your Docker port open when UFW says it is blocked?

Page 2 of 3. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

RHEL 9Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment8 editable values
04

LET’S BUILD

Probe from another network before the change

Run on a separate Linux/macOS workstation outside the host and its container networks, against your own authorized public IPv4. Do not use the host's own public IP from that same host as the outside test.

An HTTP response confirms reachability from THIS source. A timeout may come from an upstream policy; it does not prove that the host firewall blocked Docker traffic. Save the time, source network, destination and result privately.

The terminal asks for an actual public IPv6 without brackets. Press Enter if the host has none; repeat for each address where present. This optional value is read in your terminal, not stored in the browser.

bash

Independent workstation

curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' 'http://203.0.113.10:8080/'
printf "Public IPv6, without brackets (Enter if none): "
IFS= read -r public_ipv6
if [ -n "$public_ipv6" ]; then
  curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' "http://[$public_ipv6]:8080/"
fi
How do I know it worked?

You have an honest external baseline: reachable, refused or timed out, with the source recorded.

Choose who actually needs the port

A reverse proxy running directly on the host can use a loopback mapping. Another container normally uses a shared Docker network and the service name; in that architecture remove unnecessary host publishing instead. A legitimately public application still needs an intentional public entry point.

This worked change is only for an HTTP backend intended for host-local access. Before proceeding, confirm that remote clients are meant to lose direct access and that the existing host-side proxy uses the same local port. Never apply it blindly to a public API or remote database client.

05

LET’S BUILD

Back up the Compose file, then edit only ports

On the host during the agreed maintenance window, keep a private copy and open the actual file. Stop if the backup already exists. Replace the selected service's host publishing list with one explicit loopback entry. Preserve its image, mounts, environment and all other services. The YAML panel is a fragment of an existing service, not a complete replacement file.

bash

Docker host

cd '/srv/myapp' || exit 1
test ! -e compose.yaml.before-loopback || exit 1
(umask 077; cp --no-preserve=mode -- compose.yaml compose.yaml.before-loopback)
nano compose.yaml
How do I know it worked?

The original file is retained privately; only the intended service's ports list is edited.

The existing service's ports fragment

Use the current container target port and current host port. Keep the IP literal; do not leave a second wildcard or IPv6 publication beside it. Replacing a value in an override file can merge lists, which is why this recipe requires a single-file project.

yaml · compose.yaml — ports fragment

Docker host

services:
  web:
    # Keep the existing image, volumes and other service settings.
    ports:
      - "127.0.0.1:8080:80"
06

LET’S BUILD

Validate the resolved model without dumping secrets

Run on the host. The first command checks configuration validity; the second prints only the selected service's port list. It should contain one mapping whose host_ip is loopback. If you see wildcard bindings or unexpected ports, correct the source before recreation.

bash

Docker host

cd '/srv/myapp' || exit 1
sudo docker compose -f compose.yaml config --quiet || exit 1
sudo docker compose -f compose.yaml config --format json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(json.dumps(d["services"]["web"].get("ports", []), indent=2))'
How do I know it worked?

The resolved service has exactly the intended loopback mapping and no unexpected publication.

07

LET’S BUILD

Recreate only the selected service

This step briefly interrupts the selected service. On the host, recreate it with the existing image and no dependency restart. A simple container restart does not apply a changed port mapping. Do not use this shortcut for an application that needs an owning migration or coordinated rollout.

Review the runtime mapping again after recreation. Preserve persistent volumes and the original deployment identity; this command does not back up application data.

bash

Docker host

cd '/srv/myapp' || exit 1
sudo docker compose -f compose.yaml up -d --no-deps --force-recreate --pull never --no-build 'web' || exit 1
cid=$(sudo docker compose -f compose.yaml ps -q 'web')
test -n "$cid" || exit 1
sudo docker port "$cid"
sudo docker inspect "$cid" --format '{{json .NetworkSettings.Ports}}'
curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' 'http://127.0.0.1:8080/'
How do I know it worked?

Only loopback is published; the local HTTP request still reaches the application. Also verify the real authorized proxy/client path.

Explore all tutorials ↗