
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: June 25, 2025
When deploying Flask applications using Docker, it’s common for developers to encounter server connection issues.
For instance, a developer new to containerization could encounter a situation where the Flask server appears to be running inside the container but is unreachable from the outside. To clarify, the server seems invisible to the browser or the curl command even after building the image, running the container, and seemingly mapping the port correctly.
In this tutorial, we’ll present an example demonstrating the problem, provide a solution, and explore a few debugging tips.
To begin with, we introduce a relatively simple example.
Let’s use a minimal Flask app, index.py:
$ car index.py
from flask import Flask
app = Flask(__name__)
app.debug = True
@app.route('/')
def main():
return 'Hello World!'
if __name__ == '__main__':
app.run()
The Flask app above works as expected when run directly on the machine:
$ python3 index.py
* Serving Flask app 'index'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 372-382-014
Above, we see the development server binds to port 5000 on localhost (127.0.0.1). We can now visit http://127.0.0.1:5000/ to see Hello World!.
Next, let’s package the Flask app in a Docker container with the help of the Dockerfile:
FROM python:3.9
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir flask
EXPOSE 5000
CMD ["python3", "index.py"]
Further, let’s build the image:
$ docker build -t flaskappimage .
[+] Building 212.2s (8/8) FINISHED docker:desktop-linux
...
=> [1/3] FROM docker.io/library/python:3.9@sha256:e2d6f8be31a35665d3c39561ac2e96ece5b847e49ff84c462ab1d6850900ba7d 199.2s
...
=> [2/3] WORKDIR /app 1.5s
=> [3/3] COPY . /app 2.0s
...
At this point, we are ready to run the container.
After setting up everything, we run the container:
$ docker run -d -p 5000:5000 --name myflaskapp flaskappimage
9044c0db6c776762af07f8a1d21d7843099d9e949cc4af4c8b4815a241d720b2
Let’s break down the command:
Now, when we try to access the app using curl, we get:
$ curl http://127.0.0.1:5000/
curl: (52) Empty reply from server
From the browser, we also can’t access the app because 127.0.0.1 doesn’t send any data.
Let’s start by looking at the logs inside the container:
$ docker logs myflaskapp
* Serving Flask app 'index'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 100-495-488
The Flask development server binds to 127.0.0.1 inside the container:
* Running on http://127.0.0.1:5000
To explain, that address refers to the container’s interface, not that of the host. So, the server only listens within the container and not on any externally accessible interface.
Thus, the port is published, but the app is only reachable from inside the container itself, not from the host.
To resolve basic server connection issues, we can instruct Flask to listen on all interfaces with the help of host=’0.0.0.0′.
Let’s first update the index.py file:
from flask import Flask
app = Flask(__name__)
app.debug = True
@app.route('/')
def main():
return 'Hello World!'
if __name__ == '__main__':
app.run(host='0.0.0.0')
Now, Flask binds to all available network interfaces.
To implement the change, let’s rebuild the image:
$ docker build --no-cache -t flaskappimage .
Once we rebuild the image, we stop and remove the old container:
$ docker stop myflaskapp && docker rm myflaskapp
myflaskapp
myflaskapp
At this point, we run the container:
$ docker run -d -p 5000:5000 --name myflaskapp flaskappimage
369fb687b84b225d50e80681167f97eba6242b70d54eff641a98d968a83b314b
Let’s now view the logs:
$ docker logs myflaskapp
* Serving Flask app 'index'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.17.0.2:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 100-495-488
So, now we see the newline * Running on all addresses (0.0.0.0), indicating that the modifications worked as expected.
Finally, let’s try accessing the app from the host again:
$ curl http://127.0.0.1:5000/ && echo
Hello World!
We can now see Hello World! in the terminal. Here, && echo prints a newline after the output.
Let’s briefly explain why binding to 0.0.0.0 is important.
Inside the container:
Although port mapping (-p 5000:5000) instructs Docker to map port 5000 of the container to port 5000 on the host, the host may fail to reach the container even if the port is matched. In our case, we saw that this happens if the container only listens on 127.0.0.1.
Therefore, the combination of binding to 0.0.0.0 inside the app and mapping ports using the -p option in the docker run command makes the app externally accessible.
To debug basic issues, we can try several troubleshooting steps.
Firstly, we can use the curl command inside the container:
$ docker exec -it <container_id> curl 127.0.0.1:5000
Hello World!
...
If this command works here but not on the host, then the app works fine, but it’s probably not exposed outside the container.
To inspect networking issues, we can use common debugging tools such as netstat. In some cases, the image may not contain these debugging tools. For example, since we’re using a minimal Docker image like python:3.9, we don’t have access to these debugging tools. In such cases, we can temporarily install them inside a running container.
Let’s begin by opening an interactive shell:
$ docker exec -it myflaskapp bash
If bash isn’t available, we can use sh instead:
$ docker exec -it myflaskapp sh
In the interactive shell, we update package lists and install the desired tools package:
# apt update && apt install -y net-tools iputils-ping
Then, we should be able to use the respective tool:
# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN 1/python3
The output above confirms that the Flask app listens on port 5000 on all interfaces (0.0.0.0). These changes are temporary because we lose the tools once we stop or delete the container. With the help of this approach, we can quickly debug without modifying the Docker image. For persistence, we can integrate these tools into a custom image.
To avoid reinstalling tools every time the container starts, we can modify the Dockerfile to install them permanently. To achieve this, let’s modify the Dockerfile:
FROM python:3.9
WORKDIR /app
COPY . /app
# Install Flask and useful debug tools
RUN apt update && apt install -y net-tools iputils-ping && \
pip install --no-cache-dir flask
EXPOSE 5000
CMD ["python3", "index.py"]
During image build, the Dockerfile now installs net-tools and iputils-ping. It makes these tools available every time we run a container from this image.
Thus, we manage to keep the Flask app behavior predictable during development or troubleshooting containerized environments.
In this article, we addressed server connection issues in the Docker deployment of a minimal Flask app.
Deploying a Flask app using Docker is straightforward. However, a small misconfiguration, such as binding the server to the wrong interface, can make the app unreachable. By default, Flask binds to 127.0.0.1, restricting access to inside the container only. That’s why we need to bind to 0.0.0.0.
Once we understand how Docker handles networking and port exposure, such problems become easy to troubleshoot and fix. Whether we’re building microservices or just experimenting with Docker, we can now debug similar issues in the future.