Skip to content

Instantly share code, notes, and snippets.

@fardjad
Last active December 10, 2025 08:24
Show Gist options
  • Select an option

  • Save fardjad/834047a8ad7d47dd524e15aa66648f32 to your computer and use it in GitHub Desktop.

Select an option

Save fardjad/834047a8ad7d47dd524e15aa66648f32 to your computer and use it in GitHub Desktop.
[Exposing Ports of a Running Container] A method for exposing Ports of a running Docker container without having to recreate it #blog #docker #socat #stdio #tunnel

Exposing Ports of a Running Container

When running containers with Docker Compose, I sometimes want to access an unpublished port from a dependency container. For example, I sometimes need to connect directly to a database running inside a compose stack. The usual solution is to edit (docker-)compose.yml and recreate the container with a published port. This works, but it is disruptive when the service is already running.

After experimenting a bit, I discovered two ways to expose container ports without recreating the containers. I'll document those in this post.

Method 1: Mapping Ports Through Standard Streams

Note

I came up with this method to work around using legacy links feature. The downside is that it requires installing a tool like socat on the host machine and on the container.

Docker makes it easy to run arbitrary processes inside an existing container by using commands like docker exec or docker compose exec. This makes it possible to use a program like socat on the host to forward a local port into the container through standard input and output. In practice, we create a TCP listener on the host, and whenever a connection comes in, socat spawns a second socat process inside the container, and the two processes communicate through STDIO.

The workflow looks like this:

Given the following compose.yml:

services:
  my-stack:
    image: alpine/socat
    entrypoint: /bin/sh
    init: true
    command: ["-c", "sleep infinity"]
  nginx:
    image: nginx
    network_mode: "service:my-stack"
  1. Install socat on the host machine.

  2. Make sure socat is available in the container. In the example above, I'm running nginx as a sidecar of the socat container to not deal with installing socat on the nginx container. Alternatively, you can install socat using the distro package manager in the nginx container, or you can copy in a static binary using docker cp.

  3. Start the relay:

    HOST_PORT=8080
    CONTAINER_PORT=80
    SERVICE_NAME=my-stack
    
    socat TCP-LISTEN:$HOST_PORT,reuseaddr,fork \
      EXEC:"docker 'compose exec $SERVICE_NAME socat STDIO TCP4:127.0.0.1:$CONTAINER_PORT'"

Resuable Script

Here is the same approach in form of a convenient Bash script:

#!/usr/bin/env bash
# Usage: docker-expose <container-name> source-port:dest-port
# Example: docker-expose mysql 3306:3306

set -euo pipefail

if [ $# -ne 2 ]; then
    echo "Usage: $(basename $0) <container-name> source-port:dest-port"
    exit 1
fi

CONTAINER_NAME="$1"
PORTS="$2"

if [[ "$PORTS" != *:* ]]; then
    echo "Error: port mapping must be in the form source:dest"
    exit 1
fi

HOST_PORT="${PORTS%%:*}"
CONTAINER_PORT="${PORTS##*:}"

if ! command -v socat >/dev/null 2>&1; then
    echo "Error: socat is not installed on the host. Please install it first."
    exit 1
fi

# Note: This will not work for containers that are built from SCRATCH  
if ! docker exec "$CONTAINER_NAME" sh -c "command -v socat" >/dev/null 2>&1; then
    echo "Error: socat is not installed in the container '$CONTAINER_NAME'."
    echo "Please install it or copy a static binary into the container."
    exit 1
fi

echo "Forwarding host port $HOST_PORT to $CONTAINER_NAME:$CONTAINER_PORT"
echo "Press Ctrl+C to stop."

socat TCP-LISTEN:"$HOST_PORT",reuseaddr,fork \
    EXEC:"docker 'exec -i $CONTAINER_NAME socat STDIO TCP4:127.0.0.1:$CONTAINER_PORT'"

Method 2: Attaching a Network to the Container

As an alternative to the legacy link feature, we can create a network, connect it to the target container and run a new container with published ports on the same network that forwards the traffic to the target container ports.

Given the following compose.yml:

services:
  nginx:
    image: nginx
    container_name: nginx

We can run the following commands to create a network called port-exposer and connect it to the nginx container:

docker network create port-exposer
docker network connect port-exposer nginx

We can verify that the network is connected to the container by inspecting the container:

docker inspect nginx --format="{{json .NetworkSettings}}" | jq

Which outputs something similar to this:

{
  //...,
  "Ports": {
    "80/tcp": null
  },
  "Networks": {
    "whatever_default": {
      //...
    },
    "port-exposer": {
      "DNSNames": [
        //...,
        "nginx"
      ]
    }
  }
}

Then we can run an socat container to forward forward and publish a port on the host machine to a port in the target container:

HOST_PORT=8080
CONTIANER_NAME=nginx
CONTAINER_PORT=80

docker run --rm --network=port-exposer -p ${HOST_PORT}:${HOST_PORT} alpine/socat tcp-listen:${HOST_PORT},fork,reuseaddr tcp-connect:${CONTIANER_NAME}:${CONTAINER_PORT}

Once we're done, we can disconnect the network from the nginx container by running:

docker network disconnect port-exposer nginx

and remove the network by running:

docker network rm port-exposer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment