Yaman Ahlawat
Yaman Ahlawat

Reputation: 507

Nginx Serve React build and proxy_pass Django Rest api server

domain.conf looks like this

I am proxy passing the Django API server using Nginx. Nginx uses letsencrypt SSL certificates and is currently listening on port 80 and 443. Nginx perfectly serves the react build files while accessing the Django API using Axios in react app results in 502 bad gateway. Axios is trying to access "/api/v1/" as baseURL.

server {
    listen 80;
    listen [::]:80;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # redirects www to non-www. wasn't work for me without this server block
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        root /var/www/frontend;
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://localhost:8000;
        proxy_redirect default;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

part of docker-compose.yml looks like this

backend:
    build:
      context: .
      dockerfile: dockerFiles/backend/DockerFile
    tty: true
    ports:
      - "8000:8000"
    expose:
      - 8000
    volumes:
      - ./backend:/backend
    env_file:
      - backend/.env
    depends_on:
      - db

  frontend:
    image: node:latest
    command: sh start.sh
    working_dir: /frontend
    tty: true
    volumes:
      - ./frontend/:/frontend
    depends_on:
      - backend
    links: 
      - backend

  nginx:
    image: nginx:latest
    tty: true
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./config/nginx/conf.d:/etc/nginx/conf.d
      - ./frontend/dist:/var/www/frontend
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - backend

- start.sh runs yarn yarn build - domain.conf is copied from ./config/nginx/conf.d

Upvotes: 3

Views: 6502

Answers (2)

Yaman Ahlawat
Yaman Ahlawat

Reputation: 507

Adding the Final Working Config and Docker Files:

Nginx Config

upstream backend_server {
    server backend:8000;
}

server {
    listen 80;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location /api/ {
        return 301 https://$host$request_uri;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    root /var/www/frontend;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location /api/v1/ {
        proxy_pass http://backend_server/;
        proxy_set_header Host $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 $https;
        proxy_connect_timeout 360s;
        proxy_read_timeout 360s;
    }

    location / {
        try_files $uri /index.html;
    }
}

docker-compose also added certbot config with nginx

version: '3.5'

services:
  db:
    container_name: db
    image: postgres:latest
    ports:
      - "5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data/

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: dockerFiles/backend/DockerFile
    tty: true
    ports:
      - "8000:8000"
    expose:
      - 8000
    volumes:
      - ./backend:/backend
    env_file:
      - backend/.env
    depends_on:
      - db

  frontend:
    container_name: frontend
    image: node:latest
    command: sh start.sh
    working_dir: /frontend
    tty: true
    volumes:
      - ./frontend/:/frontend
    depends_on:
      - backend
    links: 
      - backend

  nginx:
    container_name: nginx
    image: nginx:latest
    tty: true
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./config/nginx/conf.d:/etc/nginx/conf.d
      - ./frontend/dist:/var/www/frontend
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    depends_on:
      - backend
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  certbot:
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"


volumes:
  postgres_data: # <-- declare the postgres volume

start.sh -- for migrating database, collect static files and start gunicorn server

# Start Gunicorn processes
echo Starting Gunicorn.
exec gunicorn <app-name>.wsgi:application -k gevent\
    --bind 0.0.0.0:8000 \
    --workers

Upvotes: 2

deathangel908
deathangel908

Reputation: 9709

Nginx passes your request through the chain of locations you write in the config, within the same order.

  • You can always check access.log to see what happens
  • You can set logs level to debug on nginx so it would tell you what happens

The correct configuration is:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location /api/ {
        proxy_pass http://frontend:8000;
        proxy_redirect default;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location / {
        root /var/www/frontend;
        try_files $uri $uri/ /index.html;
    }

}

I'm also not sure about server configuration order, but I would either remove your first server that listens 443, or put it after the first one, or at least give it a name with www.

You can also find a working example with nginx and django at pychat.org

edit

still a 502 bad gateway. nginx access.log and error.log are coming empty. The nginx terminal logs says 2019/08/21 19:22:00 [error] 9#9: *2 connect() failed (111: Connection refused) while connecting to upstream, client: 27.7.17.142, server: , request: "POST /api/v1/login/facebook/ HTTP/2.0", upstream: "http://127.0.0.1:8000/api/v1/login/facebook/", host: "example.com", referrer: "https:/example.com/login"

No processes listen for port 8000 inside your nginx container, you have the frontend in a separate container, which is NOT accessible via localhost: you need to specify http://frontend:8000. Docker has integrated DNS resolution which equals to container name.

Upvotes: 0

Related Questions