getting NGINX to work with gunicorn web application

Viewed 647

This is my first time deploying a FastAPI + gunicorn web application with NGINX and the struggle is real.

My web app is running on an AWS EC2 machine using Docker.
What I've done so far:
  1. register a domain with godaddy - let's call the domain example.club
  2. Setup a DNS A record pointing to my server's public IP Address (via godaddy DNS management page).
  3. created a site-conf.conf file and put it into /etc/nginx/sites-available. The file has the following configuration:
server {
    listen 80;
    listen 443;
    server_name example.club;

    location / {
            proxy_pass http://127.0.0.1:8080/;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  1. Linked my .conf file to /etc/nginx/sites-enabled with the following command: sudo ln -s /etc/nginx/sites-available/site-conf.conf /etc/nginx/sites-enabled/site-conf.conf

  2. Restarted my NGINX service using the command: sudo systemctl restart nginx. When looking at the journalctl logs, everything looks normal:

-- Unit nginx.service has begun starting up.
Nov 14 19:14:42 <server-address> nginx[16558]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
Nov 14 19:14:42 <server-address> nginx[16558]: nginx: configuration file /etc/nginx/nginx.conf test is successful
Nov 14 19:14:42 <server-address> systemd[1]: Failed to read PID from file /run/nginx.pid: Invalid argument
Nov 14 19:14:42 <server-address> sudo[16537]: pam_unix(sudo:session): session closed for user root
Nov 14 19:14:42 <server-address> systemd[1]: Started The nginx HTTP and reverse proxy server.
-- Subject: Unit nginx.service has finished start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit nginx.service has finished starting up.
--
-- The start-up result is done.

At this point, when opening up http://example.club I can see a "Thanks for using Amazon Linux" page.
To me, this confirms that my domain correctly points to my server's IP address + NGINX is setup correctly.

  1. Now, all I want is to "connect" my NGINX proxy with my dockerized GUNICORN + FastAPI application (in order to expose my APIs to the world).
    To do this, I use the following Dockerfile:
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
WORKDIR /app
COPY . /app
# add files to Docker environment
...
# run app
RUN pip install -r requirements.txt
EXPOSE 8080
CMD ["gunicorn", "-b", "127.0.0.1:8080", "-k", "uvicorn.workers.UvicornWorker", "main:app"]
  1. Finally, I build my image (sudo docker build -t myimage .) and run the container:
sudo docker run --name mycontainer -e PYTHONUNBUFFERED=1 -p 8080:8080 -d myimage

while making sure I publish port 8080 correctly.
The container's logs look normal:

[2020-11-14 17:32:34 +0000] [1] [INFO] Starting gunicorn 20.0.4
[2020-11-14 17:32:34 +0000] [1] [INFO] Listening at: http://127.0.0.1:8080 (1)
[2020-11-14 17:32:34 +0000] [1] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2020-11-14 17:32:34 +0000] [8] [INFO] Booting worker with pid: 8
[2020-11-14 17:32:34 +0000] [8] [INFO] Started server process [8]
2020-11-14 17:32:34,783 Started server process [8]
[2020-11-14 17:32:34 +0000] [8] [INFO] Waiting for application startup.
2020-11-14 17:32:34,783 Waiting for application startup.
[2020-11-14 17:32:34 +0000] [8] [INFO] Application startup complete.
2020-11-14 17:32:34,784 Application startup complete.

I should also mention that I opened port 8080 for TCP from any IP address by updating inbound rules in the EC2 machine's security group.

Unfortunately, when I try to access one of my APIs (http://example.club/<some-route-defined-with-FastAPI>) I get the NGINX default 404 page.

What am I doing wrong? Why isn't the NGINX proxy recognizing my routes?

2 Answers

Try deleting the default config file in /etc/nginx/sites-enabled/ (should just be called default) and in /etc/nginx/sites-available/ and add default_server on your current config in the listen 80 line.

server {
    listen 80 default_server;
    listen 443;
    server_name example.club;
    ...
}

For future reference, what I ended up doing was binding to "0.0.0.0":

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
WORKDIR /app
COPY . /app
# add files to Docker environment
...
# run app
RUN pip install -r requirements.txt
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

I also slightly modified my NGINX site-conf.conf (removing the listens):

server {
    server_name example.*;

    location / {
            proxy_pass http://127.0.0.1:8080/;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Finally, I installed python-certbot-nginx:

sudo yum install python-certbot-nginx

and ran it with:

sudo certbot --nginx -d example.club -d www.example.club

This setup allowed me to connect to my domain with HTTPS, but I had one more problem: I could still reach my webserver with HTTP by explicitly reaching for port 8080 (curl http://example.club:8080/<api-endpoint>).
To solve that, I headed over to the security group menu in the AWS website, and only allowed access to port 8080 from my machine's IP:
screenshot

Related