179

I have an app whose only dependency is flask, which runs fine outside docker and binds to the default port 5000. Here is the full source:

from flask import Flask

app = Flask(__name__)
app.debug = True

@app.route('/')
def main():
    return 'hi'

if __name__ == '__main__':
    app.run()

The problem is that when I deploy this in docker, the server is running but is unreachable from outside the container.

Below is my Dockerfile. The image is ubuntu with flask installed. The tar just contains the index.py listed above;

# Dockerfile
FROM dreen/flask
MAINTAINER dreen
WORKDIR /srv

# Get source
RUN mkdir -p /srv
COPY perfektimprezy.tar.gz /srv/perfektimprezy.tar.gz
RUN tar x -f perfektimprezy.tar.gz
RUN rm perfektimprezy.tar.gz

# Run server
EXPOSE 5000
CMD ["python", "index.py"]

Here are the steps I am doing to deploy

$> sudo docker build -t perfektimprezy .

As far as I know the above runs fine, the image has the contents of the tar in /srv. Now, let's start the server in a container:

$> sudo docker run -i -p 5000:5000 -d perfektimprezy
1c50b67d45b1a4feade72276394811c8399b1b95692e0914ee72b103ff54c769

Is it actually running?

$> sudo docker ps
CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS              PORTS                    NAMES
1c50b67d45b1        perfektimprezy:latest   "python index.py"   5 seconds ago       Up 5 seconds        0.0.0.0:5000->5000/tcp   loving_wozniak
$> sudo docker logs 1c50b67d45b1
    * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    * Restarting with stat

Yep, seems like the flask server is running. Here is where it gets weird. Lets make a request to the server:

$> curl 127.0.0.1:5000 -v
* Rebuilt URL to: 127.0.0.1:5000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: 127.0.0.1:5000
> Accept: */*
>
* Empty reply from server
* Connection #0 to host 127.0.0.1 left intact
curl: (52) Empty reply from server

Empty reply... But is the process running?

$> sudo docker top 1c50b67d45b1
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                2084                812                 0                   10:26               ?                   00:00:00            python index.py
root                2117                2084                0                   10:26               ?                   00:00:00            /usr/bin/python index.py

Now let's ssh into the server and check...

$> sudo docker exec -it 1c50b67d45b1 bash
root@1c50b67d45b1:/srv# netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:47677         127.0.0.1:5000          TIME_WAIT
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node   Path
root@1c50b67d45b1:/srv# curl -I 127.0.0.1:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 5447
Server: Werkzeug/0.10.4 Python/2.7.6
Date: Tue, 19 May 2015 12:18:14 GMT

It's fine... But not from the outside.
What am I doing wrong?

Matteo Zanoni
  • 3,429
  • 9
  • 27
Dreen
  • 6,976
  • 11
  • 47
  • 69
  • the relevant thing is "Caused by ", see http://stackoverflow.com/questions/16592568/python-requests-urllib3-raises-httplib-badstatusline-error-if-called-many-time – user2915097 May 19 '15 at 11:57
  • I'm only trying to connect once though and I'm fairly certain this is not a bug in httpie (i changed the example to curl now), nor in the server as it works fine outside docker. i have a strong feeling this is a docker config/deployment misstep issue – Dreen May 19 '15 at 12:06
  • Check in the container with `docker exec -it 1c50b67d45b1 bash` and then the usual `netstat -an` or any command you would do when you debug a Flask (tail, cat...) – user2915097 May 19 '15 at 12:15
  • @user2915097: ive added some output from within the server – Dreen May 19 '15 at 12:20
  • 'Can't connect...' @Dreen, you _can connect_, you just get an empty reply (`Connected to 127.0.0.1`) – ForceBru May 19 '15 at 12:22
  • @ForceBru: I wrote that under output from httpie initially which said `http: error: ConnectionError: HTTPConnectionPool(host='localhost', port=5000): Max retries exceeded with url: / (Caused by : '')` but you're right. Doesn't solve the problem though. – Dreen May 19 '15 at 12:33
  • I guess something is wrong in your Flask configuration – user2915097 May 19 '15 at 12:36
  • @user2915097: as I said, it runs 100% fine outside docker. I have added the entire source code of the server to my question. There is no other code involved. – Dreen May 19 '15 at 12:39
  • docker is a minimal Linux, maybe you need something you have on a full Linux, that you have not installed in docker. BTW, I have tried to clone your environment, but where can I get erfektimprezy.tar.gz ? – user2915097 May 19 '15 at 13:56
  • @user2915097: I have removed all the cruft from the source and verified that the problem still occurs (by doing everything from scratch as I described). You can grab this source, put it in index.py and `tar -pczf perfektimprezy.tar.gz index.py` and you have the tar. – Dreen May 19 '15 at 14:10
  • BTW, if you want to deploy your app in Docker without having to learn, install and configure uWSGI, Nginx and Supervisord (to get the best performance and robustness), you may want to check this image: https://hub.docker.com/r/tiangolo/uwsgi-nginx-flask/ – tiangolo Feb 20 '16 at 15:59
  • I found an interesting article on how to run Python Flask in a docker. Should help those who new to Docker https://medium.com/@yoratyo/running-flask-on-docker-dc3941d39304 – swdev Mar 31 '18 at 09:22
  • Such a great question. Very nice indeed. Helped me out a lot – the Aug 20 '21 at 10:46

8 Answers8

313

The problem is you are only binding to the localhost interface, you should be binding to 0.0.0.0 if you want the container to be accessible from outside. If you change:

if __name__ == '__main__':
    app.run()

to

if __name__ == '__main__':
    app.run(host='0.0.0.0')

It should work.

Note that this will bind to all interfaces on the host, which may in some circumstances be a security risk - see https://stackoverflow.com/a/58138250/4332 for more information on binding to a specific interface.

Adrian Mouat
  • 44,585
  • 16
  • 110
  • 102
  • This solution works. One can see the result using a full Dockerfile and python script [here](http://docker.lhsm.com.br/2007c1d0-32c3-4b28-8159-a5d02fc552fd) as this solution describes. –  Jan 04 '19 at 12:28
  • what's the difference? – Jwan622 Aug 02 '19 at 18:45
  • 3
    @Jwan622 The localhost interface is only available inside the container. 0.0.0.0 binds to all interfaces. Interfaces connect to various networks (so you could have one for wifi, lans etc) – Adrian Mouat Aug 19 '19 at 15:48
  • 2
    Make sure you remember to bind port 5000 to your container with the `-p 5000:5000` flag with your `docker run` command. – tlochner95 May 07 '20 at 00:50
  • 1
    Cant thank you enough man, i have been at it for over 3 hours. You must be sent by the gods to help me .XD – Aryman Deshwal Nov 21 '20 at 21:03
  • See Bert's answer for a more nuanced response. Yes, Flask "shouldn't be used in production", but that's no reason to completely abandon security. – scubbo Apr 07 '21 at 18:49
  • @scubbo using a specific interface can be better, I'll mention that. Why do you say Flask shouldn't be used in production? I'm pretty sure it's widely used. – Adrian Mouat Apr 09 '21 at 08:17
  • Flask itself literally prints the message `WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead`. One recommended option for deploying to production (Waitress) is described [here](https://flask.palletsprojects.com/en/1.1.x/tutorial/deploy/). EDIT: I guess I should be clearer and say "The Flask server (which is what's run by `flask run`) should not be used in Production" - it is possible to run a Flask app in a non-Flask server. – scubbo Apr 09 '21 at 21:37
  • @scubbo Ah, yeah, this is a bit more nuanced. It's the flask *server* that shouldn't be used outside out development because it doesn't scale. So for production you would use something Gunicorn to run your flask code https://flask.palletsprojects.com/en/1.1.x/deploying/ – Adrian Mouat Apr 12 '21 at 07:24
66

When using the flask command instead of app.run, you can pass the --host option to change the host. The line in Docker would be:

CMD ["flask", "run", "--host", "0.0.0.0"]

or

CMD flask run --host 0.0.0.0
davidism
  • 121,510
  • 29
  • 395
  • 339
Marku
  • 1,854
  • 15
  • 13
  • 4
    Thanks very much for this solution, I have the same problem, do you know what is the reason why `app.run(host="0.0.0.0") ` doesn't work? I also made a post for this question: https://stackoverflow.com/q/53133350/3279996 – xirururu Nov 03 '18 at 17:12
  • 1
    On this note, make sure not to use `python run.py --host=0.0.0.0 `. That gets me every once in a while due to my naming conventions. That code will appear to work but the server will run on local host. – Braden Holt Feb 14 '19 at 23:00
  • 2
    Doesn't this run the built-in flask server, which is ***not*** meant for production environments? – code_dredd Feb 28 '19 at 00:26
  • This is great, for whatever reason nearly all Flask-with-Docker info fails to use the Flask CLI, which afaik is the Flask 1.0 way to start an app. – Zach Valenta Jul 16 '19 at 19:51
  • 10
    Here in 2020 with Flask 1.1.1 with `app.run(host="0.0.0.0")` failing and `CMD ["flask", "run", "--host", "0.0.0.0" ]` working like a champ. – Joe Mar 04 '20 at 13:48
  • and how do we specify which file it should run? – CutePoison Sep 23 '22 at 14:38
13

Your Docker container has more than one network interface. For example, my container has the following:

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

if you run docker network inspect bridge, you can see that your container is connected to that bridge with the second interface in the above output. This default bridge is also connected to the Docker process on your host.

Therefore you would have to run the command:

CMD flask run --host 172.17.0.2

To access your Flask app running in a Docker container from your host machine. Replace 172.17.0.2 with whatever the particular IP address is of your container.

Bert
  • 2,134
  • 19
  • 19
  • Any advantage of using the `172.17.0.2` instead of `0.0.0.0`? Great answer, I've been looking for this for ages! – Voy Oct 01 '20 at 09:57
  • @voy Using `0.0.0.0` will cause Flask to listen all *all* IP addresses in that container. It's better to be specific, as you could unintentionally open the site to other networks. – Bert Oct 01 '20 at 11:35
  • 1
    Why does everyone seem to be using it? I had exact same reasoning as you, but I'm surprised by how every resource on exposing a server would suggest listening to `0.0.0.0`. Could it be that its dangers aren't that severe? Would you have any sources where I could catch up on the subject? – Voy Oct 02 '20 at 09:00
  • 3
    Because it's quick and easy, and people will read sites like this looking for an answer without verifying or researching for themselves. The Flask Quickstart document mentions that it's a security risk, and the Miguel Grinberg tutorial even says "But this is almost always a bad idea." – Bert Oct 02 '20 at 12:00
  • That's very useful, thanks Bert. What if I run it on a closed virtual private network? Would 0.0.0.0 be secure then? – Voy Oct 02 '20 at 12:04
  • just like @voy all the documentation uses 0.0.0.0, even [the docker docs](https://docs.docker.com/compose/gettingstarted/). I've tried to use the ip addr cmd but the container fails with code "cannot assign requested address". Would a reverse proxy like nginx solve the problem of binding to 0.0.0.0? Curious since I plan to add nginx to my production build. – sat1017 Oct 07 '20 at 03:34
10

You need to modify the host to 0.0.0.0 in the docker file. This is a minimal example

# Example of Dockerfile

FROM python:3.8.5-alpine3.12

WORKDIR /app

EXPOSE 5000
ENV FLASK_APP=app.py

COPY . /app
RUN pip install -r requirements.txt

ENTRYPOINT [ "flask"]
CMD [ "run", "--host", "0.0.0.0" ]

and the file app.py is

# app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello world"


if __name__ == "__main__":
    app.run()

Then compile with

docker build . -t deploy_flask

and run with

docker run -p 5000:5000 -t -i deploy_flask:latest

You can check the response with curl http://127.0.0.1:5000/ -v

Galuoises
  • 2,630
  • 24
  • 30
  • I struggled with this and the official docks mention the `EXPOSE` declaration, which is missing from many answers out there on the topic. – nicorellius Mar 01 '22 at 00:39
8

First of all in your python script you need to change code from

app.run()

to

app.run(host="0.0.0.0")

Second, In your docker file, last line should be like

CMD ["flask", "run", "-h", "0.0.0.0", "-p", "5000"]

And on host machine if 0.0.0.0:5000 doesn't work then you should try with localhost:5000

Note - The CMD command has to be proper. Because CMD command provide defaults for executing container.

Peyman Mohamadpour
  • 17,954
  • 24
  • 89
  • 100
Nivedita Shah
  • 99
  • 1
  • 2
6

To build on other answers:

Imagine you have two computers. Each computer has a network interface (WiFi, say), which is its public IP. Each computer has a loopback/localhost interface, at 127.0.0.1. This means "just this computer."

If you listed on 127.0.0.1 on computer A, you would not expect to be able to connect to that via 127.0.0.1 when running on computer B. After all, you asked to listen on computer A's local, private address.

Docker is similar setup; technically it's the same computer, but the Linux kernel is allowing each container to run with its own isolated network stack. So 127.0.0.1 in a container is the same as 127.0.0.1 on a different computer than your host—you can't connect to it.

Longer version, with diagrams: https://pythonspeed.com/articles/docker-connection-refused/

Itamar Turner-Trauring
  • 3,430
  • 1
  • 13
  • 17
3

For fast readers, three quick things to check:

  1. Make sure you have exposed the port in the Dockerfile.
  2. Running the command in container using flask run --host=0.0.0.0
  3. Specifying the port in your docker run command docker run -it -p5000:5000 yourImageName
Pankaj Garg
  • 1,272
  • 15
  • 21
0

In my case, binding the host to 0.0.0.0 only worked on my local environment, and it failed when deploying on a server.

Then it's working when I replaced the port with --network=host:

Before:

docker run -d -p 5000:5000 <docker_image>

After:

docker run -d --network=host <docker_image>

ps. I still used the 0.0.0.0:5000 inside the container when running the flask app.

Steven Chou
  • 1,504
  • 2
  • 21
  • 43