> ## Content Index
> Fetch the complete content index at: https://madewithlove.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Get rid of ports in your docker development setup with Traefik
- URL: https://madewithlove.com/blog/get-rid-of-ports-in-your-docker-development-setup-with-traefik/
- Published: 2021-06-04T14:37:47.000Z
- Updated: 2026-05-29T11:57:25.000Z
- Author: Peter Packet
- Tags: Engineering, Infrastructure, Tutorials

*Reading tip*: [Learn how to run an ssh tunnel in your docker based development environment to easily connect to mysql or other non-http services.](https://madewithlove.com/blog/using-an-ssh-tunnel-to-connect-to-your-dev-mysql-with-docker/)

  
When working in a Docker-based development environment, you usually map the ports of your existing Docker container (port mapping or docker port forwarding) to a port on your own machine so you can browse to the application your container serves. When your container is running, you will browse to [http://localhost:8000](http://localhost:8000/?ref=madewithlove.com) or any other port you choose to see your application in the browser.

**Change port docker**

There are some limitations to working this way, for example, when running multiple projects simultaneously. This can happen when you have to switch projects or the project grows and other applications are developed that run alongside the original application. In both these cases, you risk having containers listening on the same docker port, which will not work. You could give each container a different port to listen to, but then you have to start keeping track of which ports are already in use. This approach is not scalable and is also simply annoying.

Something that has always bothered me when developing on containers is that you actually have to browse to localhost and then add the desired port to the URL. Of course, this is really a minor thing, but it bothers me nonetheless. It’s just plain ugly

**Docker Port Forwarding Explained**

Docker port forwarding is a mechanism that enables communication between a Docker container and the host system or other containers by mapping network ports. Docker forwards incoming traffic from a specific port on the host to a corresponding port within the container by specifying port mappings during container creation or runtime. This allows external systems or services to access the containerized application through the host system's network interface, facilitating seamless network connectivity and interaction.

**Incoming: Traefik Tutorial**

For the last couple of years, I have solved this problem by using Traefik (pronounced like traffic). Traefik is a reverse proxy running in Docker but is also aware of Docker. In this post, I’m going to set up a system that makes our Docker containers accessible through a normal domain name without having to worry about port conflicts.

We will start by running a simple container exposing port 80 and have it mapped to port 8080 on our host system. Run `docker-compose up` with the following `docker-compose.yml` file.

```yaml
 version: "3.8"
 services:
   apache:
     image: httpd:2.4-alpine
     ports:
       - 8080:80 
```

Now go to [http://localhost:8080](http://localhost:8080/?ref=madewithlove.com) and you will see the test page of Apache.

![docker reverse proxy traefik](https://madewithlove.com/blog/content/images/2026/05/docker-compose-1--1024x677.png)

Great success so far! Run `docker-compose down` to stop the container and clean everything up.

Now that we have assured Docker is working as expected, we will start setting up Traefik. Let’s start by making a new `docker-compose.yml` file with one service and one (docker) network.

```yaml
 version: "3"
 services:
   traefik:
     restart: always
     image: traefik:v2.4
     container_name: traefik
     ports:
       - 80:80
       - 8080
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock:ro
       - ./traefik.toml:/etc/traefik/traefik.toml
     labels:
       - traefik.enable=true
       - traefik.http.routers.api.rule=Host(`monitor.docker`)
       - traefik.http.routers.api.entrypoints=web
       - traefik.http.routers.api.service=api@internal
       - traefik.port=8080"
     networks:
       - proxy
 networks:
   proxy:
     driver: bridge
     name: proxy 
```

Most of this should be pretty familiar if you have already used docker-compose, so I’ll just go over the interesting lines.

I stated before that Traefik is aware of Docker. This means that, while it is actually running in a container, Traefik knows that Docker is running. It will listen to Docker and inspect the labels on every container that is started. If it finds labels it can do something with, it will work its magic. For Traefik to listen to Docker, it needs access to the Docker socket.

```yaml
 volumes:
   - /var/run/docker.sock:/var/run/docker.sock:ro 
```

Traefik comes by default with an API and a dashboard. It exposes both on port 8080\. Since we don’t want to use ports anymore, we will add labels to the container so that it exposes both projects on the url: `http://monitor.docker`. I’ll explain these labels a little later.

```yaml
    labels:
       - traefik.enable=true
       - traefik.http.routers.api.rule=Host(`monitor.docker`)
       - traefik.http.routers.api.entrypoints=web
       - traefik.http.routers.api.service=api@internal
       - traefik.port=8080 
```

We create a network, called proxy. All containers that need to be made accessible through Traefik will have to be in this network.

```yaml
 networks:
   proxy:
     driver: bridge
     name: proxy 
```

Traefik itself can be configured using a file called `traefik.toml`. Here’s what mine looks like:

```toml
 [api]
   insecure = true
 [entryPoints.web]
   address = ":80"
 [providers.docker]
   exposedByDefault = false
   network = "proxy" 
```

As you see, the configuration is rather simple. Let’s go over the different sections and explain in short what they do.

Traefik provides a dashboard by default. This setting makes sure that you can access it without a password or other security measures. Since we’re only running this locally, that’s perfectly fine. Don’t do this in production.

```toml
[api]
  insecure = true
```

We need to define entry points. From the Traefik documentation: entry points are the network entry points into Traefik. They define the port which will receive the packets and whether to listen for TCP or UDP.

We will define an entry point called web and have it listen on port 80.

```toml
 [entryPoints.web]
   address = ":80" 
```

We also need to define a provider. We will use docker as a provider. From the documentation: “Providers discover the services that live on your infrastructure (their IP, health, …).” We will configure a docker provider. It connects to containers on the proxy network. We set [**exposedByDefault**](https://doc.traefik.io/traefik/providers/docker/?ref=madewithlove.com#exposedbydefault) to false since we don’t need to have every container exposed by Traefik.

```toml
 [providers.docker]
   exposedByDefault = false
   network = "proxy" 
```

There is one last thing we need to do before we can spin up Traefik and that is to point the `monitor.docker` url to localhost. Add this line to your hosts file:

```yaml
127.0.0.1 monitor.docker
```

Run `docker-compose up` and off we go!

If you browse to [http://monitor.docker/](http://monitor.docker/?ref=madewithlove.com) you should see the Traefik dashboard.

![traefik docker](https://madewithlove.com/blog/content/images/2026/05/work-with-traefik-2-1024x629.jpg)

We still need to make our Apache container accessible through Traefik. Traefik will look for certain labels on each container that is started, so we just need to provide the right ones when configuring the Apache container. We also have to add our container to the proxy network. This is what the updated `docker-compose.yml `file for our Apache service could look like.

```yaml
 version: "3.8"
 services:
   apache:
     image: httpd:2.4-alpine
     container_name: apache
     ports:
       - 80
     labels:
       - traefik.enable=true
       - traefik.http.routers.apache.rule=Host(`apache.docker`)
       - traefik.http.routers.apache.entrypoints=web
       - traefik.port=80
     networks:
       - proxy

 networks:
   proxy:
     external: true 
```

I’ll explain the different labels used here.

**Expose host port to docker container** (dockerfile port expose)

First of all, we need to make sure Traefik picks up this container. Because we disabled the *`exposedByDefault`* setting in the Traefik config, we need to explicitly say if we want the container exposed.

```yaml
traefik.enable=true
```

We define a router named “apache” and make sure it routes the apache.docker url to our container.

```yaml
traefik.http.routers.apache.rule=Host(`apache.docker`)
```

We want the router we defined to use the web entrypoint which listens on port 80, the default HTTP port.

```yaml
traefik.http.routers.apache.entrypoints=web
```

And last, but not least, we tell Traefik that our container is listening on port 80.

```yaml
traefik.port=80
```

Add the `apache.docker` url to your hosts file so it points to 127.0.0.1

```yaml
127.0.0.1 apache.docker
```

Run `docker-compose up` with your docker-compose file for Apache. Now you should be able to browse to [http://apache.docker/](http://apache.docker/?ref=madewithlove.com) and you should see the Apache test page.

![traefik docker](https://madewithlove.com/blog/content/images/2026/05/traefik-docker-3-1024x629.jpg)

No more annoying ports in your dev setup ever again! Hurray!

There are several improvements I can think of:

- Use a tool like `dnsmasq` to stop having to configure your hosts file for every new service
- Make your database accessible in a similar way
- Add HTTPS

But those are maybe for another blog post.

Next up: [Using an SSH tunnel to connect to your dev MySQL with Docker](https://madewithlove.com/blog/using-an-ssh-tunnel-to-connect-to-your-dev-mysql-with-docker/)