How to get NGINX running in Docker to reload nginx.conf configuration - docker

I am trying to get a NGINX based reverse proxy to work in Windows/WSL2 environment. I am very new to Docker and NGINX world. I am able to get the following command to work
docker run --name nginx-test -p 8080:80 -v /home/skotekar/nginx.conf:/etc/nginx/nginx.conf:ro -v /mnt/d/site1/wwwroot:/usr/share/nginx/html:ro -d nginx:alpine
I can then browse http://localhost:8080 and view my static content just fine. As you see from the command I have a default nginx.conf in my local /home folder which gets mapped into NGINX Docker when running. It works fine the first time.
Now if I stop the container using:
docker container stop nginx-test
Then make changes to the nginx.conf file in my /home directory and want to start the container with updated configuration using following command:
docker container start nginx-test
But this command fails gives me a very confusing message:
Error response from daemon: OCI runtime create failed: container_linux.go:370: starting container process caused: process_linux.go:459: container init caused: rootfs_linux.go:59: mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-20.04/c0d0caa87ff063ee46265048f5b1ee489a8945669d39c6f6110cd578b8cda1ed" to rootfs at "/var/lib/docker/overlay2/4e6b279945acb06200b3677272774f4b5fbb6a619214decbca8c594dbbe3b8ec/merged/etc/nginx/nginx.conf" caused: no such file or directory: unknown
Only way to get it back running is to delete the container and use the first command again. Any idea how to get this working. It will be easier if I could just restart my container after making changes to config until I figure out the correct reverse proxy settings I need.
Thanks

You do not need to restart container to reload new config. Nginx can hot-reload config without restarting.
Once you have mounted volume, you can make changes and they will be reflected in container immediately.
To test your config just execute this command:
docker exec nginx-test nginx -t
To reload new config:
docker exec nginx-test nginx -s reload
Edit! Access Windows host from Docker running in the WSL2
As per comments i am very curious about your issues, because i haven't seen them in my career.
So my steps to reproduce your use case is:
1. Download any web application for windows
I chose caddy web server as it is single binary and I know it. It is similar application to Nginx
https://caddyserver.com/download
2. Setup simple webpage on Windows Host
I prepared Caddyfile - Config for Caddy Web Server:
:80
respond "Hello, world from Caddy on Windows!"
Then i put this Caddyfile in the same directory, where I have Caddy Server binary:
PS C:\Users\Daniel\Downloads\caddy> ls
Directory: C:\Users\Daniel\Downloads\caddy
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 29.01.2021 11:59 52 Caddyfile
-a---- 29.01.2021 11:55 34081792 caddy_windows_amd64.exe
3. Start Web Server on Windows and verify it.
To start web server run: ./caddy.exe run
Example:
PS C:\Users\Daniel\Downloads\caddy> .\caddy_windows_amd64.exe run
2021/01/29 11:01:27.520 ←[34mINFO←[0m using adjacent Caddyfile
2021/01/29 11:01:27.528 ←[34mINFO←[0m admin admin endpoint started {"address": "tcp/localhost:2019", "enforce_origin": false, "origins": ["localhost:2019", "[::1]:2019", "127.0.0.1:2019"]}
2021/01/29 11:01:27.529 ←[34mINFO←[0m tls.cache.maintenance started background certificate maintenance {"cache": "0xc000497490"}
2021/01/29 11:01:27.529 ←[34mINFO←[0m http server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv0", "http_port": 80}
2021/01/29 11:01:27.530 ←[34mINFO←[0m tls cleaned up storage units
2021/01/29 11:01:27.532 ←[34mINFO←[0m autosaved config {"file": "C:\\Users\\Daniel\\AppData\\Roaming\\Caddy\\autosave.json"}
2021/01/29 11:01:27.532 ←[34mINFO←[0m serving initial configuration
Now verify if it is working. Go to your browser and visit the http://localhost/ page:
4. Now verify you have WSL2 running on the windows host:
PS C:\Users\Daniel> wsl.exe --list --all -v
NAME STATE VERSION
* Ubuntu-20.04 Running 2
If yes shell there with command wsl
5. Start docker daemon
daniel#DESKTOP-K8UQA2E:~$ sudo service docker start
* Starting Docker: docker
6. Check your IPv4 address for WSL Network adapter in the Windows and Linux
Open powershell and execute the ifconfig command, then find WSL network adapter:
Ethernet adapter vEthernet (WSL):
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::e96c:c3d6:464e:2a3b%72
IPv4 Address. . . . . . . . . . . : 172.20.240.1
Subnet Mask . . . . . . . . . . . : 255.255.240.0
Default Gateway . . . . . . . . . :
Your Windows IP is 172.20.240.1
Then go to WSL and execute curl 172.20.240.1, to check if your hosts are connected.
daniel#DESKTOP-K8UQA2E:~$ curl 172.20.240.1
Hello, world from Caddy on Windows!d
Now figure out the Linux Host IP with the ip a command and see IP in the same network as Windows:
5: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:ce:28:8e brd ff:ff:ff:ff:ff:ff
inet 172.20.252.177/20 brd 172.20.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fece:288e/64 scope link
valid_lft forever preferred_lft forever
7. Prepare simple nginx configuration
worker_processes 5; ## Default: 1
worker_rlimit_nofile 8192;
events {
worker_connections 4096; ## Default: 1024
}
http {
index index.html index.htm index.php;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
server_names_hash_bucket_size 128; # this seems to be required for some vhosts
server { # simple load balancing
listen 80;
location / {
return 200 "Hello World from Nginx in Linux";
}
location /windows {
proxy_pass http://172.20.240.1/;
}
}
}
This is very simple config
8. Start the docker with host networking mode.
If you do not want to use the host networking, you have to do forwarding packets from docker network to the wsl network because your docker will not have access to windows host directly.
But let's say you can start container with host networking.
Run this command:
daniel#DESKTOP-K8UQA2E:~/nginx-test$ sudo docker run -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf --name="nginx-local" --network=host -d nginx:latest
Verify your docker is working fine:
daniel#DESKTOP-K8UQA2E:~/nginx-test$ sudo docker logs nginx-local
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
daniel#DESKTOP-K8UQA2E:~/nginx-test$ sudo docker ps | grep nginx
de9873314b77 nginx:latest "/docker-entrypoint.…" 20 seconds ago Up 19 seconds
8. Try to get response from linux and windows
daniel#DESKTOP-K8UQA2E:~/nginx-test$ curl localhost
Hello World from Nginx in Linux
daniel#DESKTOP-K8UQA2E:~/nginx-test$ curl localhost/windows
Hello, world from Caddy on Windows!
daniel#DESKTOP-K8UQA2E:~/nginx-test$

Related

Connection refused: when uwsgi and nginx in different containers

I am trying to setup two docker containers(yes separate without docker-compose): one with nginx and one with uwsgi with basic flask app.
I run containers in same network within docker
My nginx config for site added/linked to sites-enabled(everything else is default):
server {
listen 80;
server_name 127.0.0.1;
location / {
include uwsgi_params;
uwsgi_pass 0.0.0.0:8080;
}
}
My uwsgi.ini
[uwsgi]
module = app:app
master = true
processes = 2
socket = 0.0.0.0:8080
uwsgi entry point in docker looks like
.local/bin/uwsgi --ini uwsgi.ini
Containers run fine on their own - uwsgi receives request on 8080 and nginx receives expected requests. How ever when I try to access 127.0.0.1 i get 502 status code and nginx logs error:
1 connect() failed (111: Connection refused) while connecting to
upstream, client: 192.168.4.1, server: 127.0.0.1, request: "GET /
HTTP/1.1", upstream: "uwsgi://0.0.0.0:8080", host: "127.0.0.1"
By googling i find solution that rather use one container and some_socket.sock as file or use docker compose. Apparently problem with permissions, but I do not know how to solve them or diagnose.
I launch containers with these commands:
docker run --network app_network --name nginx --rm -p 80:80 my_nginx
docker run --network app_network --name flaskapp --rm -p 8080:8080 my_uwsgi
EDIT
You can simply use the hostname of the docker container in the uwsgi_pass directive as both docker containers are on the same subnet.
location / {
include uwsgi_params;
uwsgi_pass flaskapp:8080;
}
0.0.0.0 isn't the IP address of the server, it essentially tells the server to be hosted on every IP that the device has allocated.
To connect to it from nginx, you will need to use the IP address of the container instead.
You can find the IP address of the container running uWsgi with the following command:
docker inspect CONTAINER_ID
Where CONTAINER_ID is the ID of the container you started uwsgi in.
From here you can update the nginx config as follows:
uwsgi_pass IP_ADDRESS:8080;
Where IP_ADDRESS is the one you found from the command above
You can also set the ip address of the container when you start it with the following option
--ip <ip>
Be careful, however, to ensure that the IP address you set is in the same subnet as the standard IP's assigned.

Connection between containers [duplicate]

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?
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.
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
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.
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
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.
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/
For fast readers, three quick things to check:
Make sure you have exposed the port in the Dockerfile.
Running the command in container using flask run --host=0.0.0.0
Specifying the port in your docker run command docker run -it -p5000:5000 yourImageName
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.

Docker doesn't expose port [duplicate]

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?
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.
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
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.
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
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.
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/
For fast readers, three quick things to check:
Make sure you have exposed the port in the Dockerfile.
Running the command in container using flask run --host=0.0.0.0
Specifying the port in your docker run command docker run -it -p5000:5000 yourImageName
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.

Can't connect to docker container on localhost [duplicate]

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?
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.
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
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.
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
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.
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/
For fast readers, three quick things to check:
Make sure you have exposed the port in the Dockerfile.
Running the command in container using flask run --host=0.0.0.0
Specifying the port in your docker run command docker run -it -p5000:5000 yourImageName
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.

Docker tutorial, localhost:4000 is inaccessible

Following the tutorial on https://docs.docker.com/get-started/part2/.
I start my docker container with docker run -p 4000:80 friendlyhello
and see
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:8088/ (Press CTRL+C to quit)
But it's inaccessible from the expected path of localhost:4000.
$ curl http://localhost:4000/
curl: (7) Failed to connect to localhost port 4000: Connection refused
$ curl http://127.0.0.1:4000/
curl: (7) Failed to connect to 127.0.0.1 port 4000: Connection refused
Okay, so maybe it's not on my local host. Getting the container ID I retrieve the IP with
docker inspect --format '{{ .NetworkSettings.IPAddress }}' 7e5bace5f69c
and it returns 172.17.0.2 but no luck! curl continues to give the same responses. I can confirm something is running on 4000....
lsof -i :4000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
com.docke 94812 travis 18u IPv6 0x7516cbae76f408b5 0t0 TCP *:terabase (LISTEN)
I'm pulling my hair out on this. I've read through the troubleshooting guide and can confirm
* not on a proxy
* don't use a custom dns
* I'm having issues connecting to docker, not docker connecting to my pip server.
Running the app.py with python app.py the server starts and I'm able to hit it. What am I missing?
Did you accidentally put port=8088 at the bottom of your app.py file? When you are running this the last line of your output is saying that your python app is exposed on port 8088 not 80.
To confirm you can run either modify the app.py file and rebuild the image, or alternatively you could run: docker run -p 4000:8088 friendlyhello which would map your local port 4000 to 8088 in the container.
Try to run it using:
docker run -p 4000:8088 friendlyhello
As you can see from the logs, your app starts on port 8088, but you connect 4000 to 80 where on 80, nothing is actually listening.

Resources