1. Overview

If we want to communicate with a Docker container from our host machine, we need to have a port mapping. Sometimes, we may start a container without mapping a port that we’ll need later on.

In this tutorial, we’ll discuss the importance of port mapping in Docker. We’ll also explore different ways of adding a new port mapping once a Docker container is launched.

2. Why We Use Port Mapping

Port mapping is used to access the services running inside a Docker container. We open a host port to give us access to a corresponding open port inside the Docker container. Then all the requests that are made to the host port can be redirected into the Docker container.

Port mapping makes the processes inside the container available from the outside.

While running a new Docker container, we can assign the port mapping in the docker run command using the -p option:

$ docker run -d -p 81:80 --name httpd-container httpd

The above command launches an httpd container, and maps the host’s port 81 to port 80 inside that container.

By default, the httpd server listens on port 80.

So we can now access the application using port 81 on the host machine:

$ curl http://localhost:81
<html><body><h1>It works!</h1></body></html>

It’s not mandatory to perform port mapping for all Docker containers. Often, we’ll avoid opening host ports in order to keep the services of the container private, or only visible to sibling containers in the same Docker network.

3. Ways to Assign a New Port Mapping to a Running Container

Let’s consider a situation where we forgot to do the port mapping when starting the Docker container. There would be no way to access the service from a TCP/IP connection on the host.

There are three ways to deal with this:

  • start over by stopping the existing container, and relaunching a new one with the same original Docker image
  • commit the existing container, and relaunch a new container from the committed Docker image, keeping the state of the container we’re trying to access
  • add a new port mapping by manipulating the Docker configuration files

Let’s dive deeper into each of these solutions.

4. Relaunch the Container

This is a naive solution where we remove the running Docker container, and relaunch it using the same Docker image. But this time, we add the port mappings that we forgot to add in the old container.

This is the simplest and most straightforward approach, but it doesn’t suit all situations.

Consider a case where we launch the Docker container (without any port mapping), and perform some operations inside it, such as installing a few packages, launching processes, updating files, and so on.

After all of these changes, we realize that the port mapping is missing. Now if we launch a new Docker container using the same old Docker image, we’ll lose all those changes.

The next approach comes to the rescue under these circumstances.

However, we should note that it’s a good practice to use Docker volumes for our important data to make it easy to replace containers.

5. Relaunch From Docker Commit

Instead of launching a new container from zero, we can commit the old Docker container to create a new Docker image, and use that to start a new container with the right ports open.

Since we’re committing the old Docker container, the persistent state of the first will be available in the newly launched one.

Let’s try this out.

First, we’ll stop our Docker container and create its image using the docker commit command:

$ docker stop httpd-container
httpd-container
$ docker commit httpd-container httpd-image
sha256:33da33fcad051c90ac9b7dea9b2dbda442767e05ddebd8d6db8ac6893ef4ef40

Next, we’ll remove the container and start a fresh container using the image that we just created.

We need to make sure we add/update the port mappings in the run command this time:

$ docker rm httpd-container
httpd-container
$ docker run -d -p 83:80 --name httpd-container httpd-image
dd2535c477ad74e80b3642abca9055efacb89eaf14572b91f91bf20cd3f0cbf3

Now we have a new container with the right port mappings to pick up where the previous one left off.

6. Reconfigure Docker in Flight

In the two approaches we discussed so far, we removed the existing Docker container and started up a new one. Although the container works the same way, its metadata is completely different from the previous one.

Now let’s look at how to modify the existing Docker container instead.

First, let’s run a new Docker container without any port mapping:

$ docker run -d --name httpd-container httpd 
a0ed1c9fc60c358d53400dc244e94ef0db4d1347e70bd4be725a890b062ebbe7
$ docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS          PORTS       NAMES
a0ed1c9fc60c   httpd     "httpd-foreground"   1 second ago        Up 1 second     80/tcp      httpd-container

The docker run command returns the full docker container ID, which is 64 characters in length.

Alternatively, this ID can also be found using the docker inspect command:

$ docker inspect --format="{{.Id}}" httpd-container
a0ed1c9fc60c358d53400dc244e94ef0db4d1347e70bd4be725a890b062ebbe7

We’ll use the ID later to find the Docker configuration file path.

6.1. Stop Docker Container and Docker Service

The first step of reconfiguring the running container is to stop it.

This can be done using the docker stop command:

$ docker stop httpd-container
httpd-container

Since we’ll be updating the config files of the Docker container, we also need to stop the Docker service itself.

We should note that this will take all Docker containers down until we’re finished:

$ systemctl stop docker

6.2. Find Config Files

All the config files related to Docker containers, images, volumes, and networks can be found inside the /var/lib/docker directory. This directory will differ between Windows and macOS.

We’ll focus mainly on two Docker container config files, hostconfig.json and config.v2.json.

These files are present inside this directory:

/var/lib/docker/containers/<ID>/

Here, ID denotes the complete Docker container ID that we computed at the beginning of Section 6.

This is what its value came out to be:

a0ed1c9fc60c358d53400dc244e94ef0db4d1347e70bd4be725a890b062ebbe7

So, in our case, the config files are present inside this directory:

$ ls /var/lib/docker/containers/a0ed1c9fc60c358d53400dc244e94ef0db4d1347e70bd4be725a890b062ebbe7/
a0ed1c9fc60c358d53400dc244e94ef0db4d1347e70bd4be725a890b062ebbe7-json.log  checkpoints  config.v2.json  hostconfig.json  hostname  hosts  mounts  resolv.conf  resolv.conf.hash

6.3. Update Config Files

Let’s first update the hostconfig.json file. We’ll need to search for the PortBindings key in this JSON file. This field contains all the port mappings for that particular container.

Since we didn’t add any port mappings while running the Docker container, the PortBindings key will be empty for the httpd-container:

{
  ...
  ...
  "PortBindings": {},
  ...
  ...
}

Now let’s assign host port 82 to port 80 of the Docker container httpd-container by updating the PortBindings in the JSON file:

{
  ...
  ...
  "PortBindings": {"80/tcp":[{"HostIp":"","HostPort":"82"}]},
  ...
  ...
}

Once the port is mapped, we need to expose port 80 of the Docker container in the config.v2.json file.

We should add the ExposedPorts field (if not already present) inside the Config key:

{
  ...
  "Config":
  {
    ...
    "ExposedPorts":
    {
      "80/tcp":{}
    },
    ...
  }
}

By doing so, we inform the Docker daemon that the container is listening on port 80 within that network interface.

Multiple ports can be exposed by passing the values as comma-separated key-value pairs:

{
  ...
  "Config":
  {
    ...
    "ExposedPorts":
    {
      "80/tcp":{},
      "82/tcp":{},
      "8080/tcp":{}
    },
    ...
  }
}

6.4. Verify the Changes

It’s time to verify the changes we’ve made so far.

First, let’s start the Docker service:

$ systemctl start docker

Now let’s start the docker container httpd-container, and verify the port mapping using the docker ps command:

$ docker start httpd-container
httpd-container
root@ip-172-31-40-187:~# docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS          PORTS                               NAMES
a0ed1c9fc60c   httpd     "httpd-foreground"   1 hours ago         Up 1 seconds    0.0.0.0:82->80/tcp, :::82->80/tcp   httpd-container

Here we can see that the port mapping has been updated.

We can now access the httpd service from outside the Docker environment using port 82:

$ curl http://localhost:82
<html><body><h1>It works!</h1></body></html>

In this approach, the container id and other container-related metadata are preserved, since we’re not replacing the original Docker container.

6.5. Update the Port Mapping of a Running Container

We can also modify existing port mappings of any Docker container using the same procedure.

The only difference is that the PortBindings key in the hostconfig.json file won’t be empty. We just need to update which port numbers we’re using.

If we update the Docker container TCP port in the port mapping, we also need to expose the same port inside the config.v2.json file.

Finally, we’ll start the Docker service and the Docker container to bring the changes into effect.

7. Conclusion

In this article, we learned three different approaches for starting containers with the port mappings we need.

The first two approaches remove an existing Docker container, and spin up a replacement.

Finally, we demonstrated how to change the port mapping in an existing container by updating various Docker system config files while the Docker service was stopped.