Docker-compose + Nginx + Certbot + Simple Django Rest Framework app - docker

I am trying to deploy a simple Django Rest Framework app to the production server using Docker. My aim is to install Nginx with a proxy and Certbot for a regular Let'sEncrypt SSL at the same time. I manage my dependencies in DockerFiles and docker-compose.
So the folder structure has the following view:
app
DockerFile
nginx
DockerFile
init-letsencrypt.sh
nginx.conf
docker-compose.yml
My idea is to hold all the configs in app/docker-compose.yml and start many different instances from the same source. But I do not have any nginx or certbot config in app/DockerFile - that's only for Django Rest Framework and that works well. But in docker-compose.yml I have the following code:
version: '3'
'services':
app:
container_name: djangoserver
command: gunicorn prototyp.wsgi:application --env DJANGO_SETTINGS_MODULE=prototyp.prod_settings --bind 0.0.0.0:8000 --workers=2 --threads=4 --worker-class=gthread
build:
context: ./api
dockerfile: Dockerfile
restart: always
ports:
- "8000:8000"
depends_on:
- otherserver
otherserver:
container_name: otherserver
build:
context: ./otherserver
dockerfile: Dockerfile
restart: always
nginx:
build: ./nginx
ports:
- 80:80
depends_on:
- app
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
This makes me to build "app", "otherserver", "nginx" and "certbot".
The most important parts are in "nginx" folder.
I used this manual and cloned file "init-letsencrypt.sh" from the source just the way it was described. Then I tried to bash it:
nginx/DockerFile:
FROM nginx:1.19.0-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
RUN mkdir -p /usr/src/app
COPY init-letsencrypt.sh /usr/src/app
WORKDIR /usr/src/app
RUN chmod +x init-letsencrypt.sh
ENTRYPOINT ["/usr/src/app/init-letsencrypt.sh"]
In nginx/nginx.conf I have the following code:
upstream django {
server app:8000;
}
server {
listen 80;
server_name app.com www.app.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name app.com www.app.com;
access_log /var/log/nginx-access.log;
error_log /var/log/nginx-error.log;
ssl_certificate /etc/letsencrypt/live/app.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location ^/static/rest_framework/((img/|css/|js/|fonts).*)$ {
autoindex on;
access_log off;
alias /usr/src/app/static/rest_framework/$1;
}
location / {
proxy_pass http://django;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_body_buffer_size 256k;
proxy_connect_timeout 120;
proxy_send_timeout 120;
proxy_read_timeout 120;
proxy_buffer_size 64k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
client_max_body_size 100M;
}
}
So, with this configuration when I do "docker-compose build", the build works without any errors and everything is successfully built. But as soon as I do "docker-compose up" I have the problem that certbot and nginx are not connect and the app is working only when I use http://app.com:8000 instead of https://app.com.
In console I do not have any errors.
What do I do wrong? What have I missed? Any help will be appreciated.

I see in your setup you try to run let's encrypt from within the nginx container. But I believe there are two better way that I describe in details here and here.
The idea behind the first method is to have a docker-compose file to initiate the letsencrypt certificate, and another docker-compose file to run the system and renew the certificate.
So without further ado, here is the file structure and content that is working really well for me (you still need to adapt the files to suit your needs)
./setup.sh
./docker-compose-initiate.yaml
./docker-compose.yaml
./etc/nginx/templpates/default.conf.template
./etc/nginx/templpates-initiation/default.conf.template
The setup in 2 phases:
In the first phase "the initiation phase" we will run an nginx container, and a certbot container just to obtain the ssl certificate for the first time and store it on the host ./etc/letsencrypt folder
I the second phase "the operation phase" we run all necessary services for the app including nginx that will use the letsencrypt folder this time to serve https on port 443, a certbot container will also run (on demand) to renew the certificate. We can add a cron job for that. So the setup.sh script is a simple convenience script that runs the commands one after another:
#!/bin/bash
# the script expects two arguments:
# - the domain name for which we are obtaining the ssl certificatee
# - the Email address associated with the ssl certificate
echo DOMAIN=$1 >> .env
echo EMAIL=$2 >> .env
# Phase 1 "Initiation"
docker-compose -f ./docker-compose-first.yaml up -d nginx
docker-compose -f ./docker-compose-first.yaml up certbot
docker-compose -f ./docker-compose-first.yaml down
# Phase 2 "Operation"
crontab ./etc/crontab
docker-compose -f ./docker-compose.yaml up -d
Phase 1: The ssl certificate initiation phase:
./docker-compose-initiate.yaml
version: "3"
services:
nginx:
container_name: nginx
image: nginx:latest
environment:
- DOMAIN
ports:
- 80:80
volumes:
- ./etc/nginx/templates-initiate:/etc/nginx/templates:ro
- ./etc/letsencrypt:/etc/letsencrypt:ro
- ./certbot/data:/var/www/certbot
certbot:
container_name: certbot
image: certbot/certbot:latest
depends_on:
- nginx
command: >-
certonly --reinstall --webroot --webroot-path=/var/www/certbot
--email ${EMAIL} --agree-tos --no-eff-email
-d ${DOMAIN}
volumes:
- ./etc/letsencrypt:/etc/letsencrypt
- ./certbot/data:/var/www/certbot
./etc/nginx/templates-initiate/default.conf.template
server {
listen [::]:80;
listen 80;
server_name $DOMAIN;
location ~/.well-known/acme-challenge {
allow all;
root /var/www/certbot;
}
}
Phase 2: The operation phase
./docker-compose.yaml
services:
app:
{{your_configurations_here}}
{{other_services...}}:
{{other_services_configuraitons}}
nginx:
container_name: nginx
image: nginx:latest
restart: always
environment:
- DOMAIN
depends_on:
- app
ports:
- 80:80
- 443:443
volumes:
- ./etc/nginx/templates:/etc/nginx/templates:ro
- ./etc/letsencrypt:/etc/letsencrypt
- ./certbot/data:/var/www/certbot
- /var/log/nginx:/var/log/nginx
certbot:
container_name: certbot
image: certbot/certbot:latest
depends_on:
- nginx
command: >-
certonly --reinstall --webroot --webroot-path=/var/www/certbot
--email ${EMAIL} --agree-tos --no-eff-email
-d ${DOMAIN}
volumes:
- ./etc/letsencrypt:/etc/letsencrypt
- ./certbot/data:/var/www/certbot
./etc/nginx/templates/default.conf.template
server {
listen [::]:80;
listen 80;
server_name $DOMAIN;
return 301 https://$host$request_uri;
}
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name $DOMAIN;
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://app:80;
}
}
The second method uses two docker images: http-proxy and http-proxy-acme-companion that were developed specifically for this reason. I suggest looking at the blog post for further details.

As I see, you havenot exposed port 443 for nginx container:
nginx:
build: ./nginx
ports:
- 80:80
- 443:443
depends_on:
Add more 443 port.

Related

Redirect Odoo 8069 to HTTPS without VPC config (AWS/VPS)

I created a Github repo weeks ago with Docker Compose, Odoo, PostgreSQL, Certbot, Nginx as a proxy server, and a little bit of PHP stuff (Symfony) -> https://github.com/Inushin/dockerOdooSymfonySSL When I was trying the config I found that NGINX worked as it was supposed to and you get the correct HHTP -> HTTPS redirect, BUT if you put the port 8069, the browser goes to HTTP. One of the solutions should be configured de another VPC, but I was thinking about using this repo for other "minimal VPS services" and not needing another VPC, so... how could I solve this? Maybe from Odoo config? Is something missing in the NGINX conf?
NGINX
#FOR THE ODOO DOMAIN
server {
listen 80;
server_name DOMAIN_ODOO;
server_tokens off;
location / {
return 301 https://$server_name$request_uri;
}
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
server_name DOMAIN_ODOO;
server_tokens off;
location / {
proxy_pass http://web:8069;
proxy_set_header Host DOMAIN_ODOO;
proxy_set_header X-Forwarded-For $remote_addr;
}
ssl_certificate /etc/letsencrypt/live/DOMAIN_ODOO/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_ODOO/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
docker-compose.yml
nginx:
image: nginx:1.15-alpine
expose:
- "80"
- "443"
ports:
- "80:80"
- "443:443"
networks:
- default
volumes:
- ./data/nginx:/etc/nginx/conf.d/:rw
- ./data/certbot/conf:/etc/letsencrypt/:rw
- ./data/certbotSymfony/conf:/etc/letsencrypt/symfony/:rw
- ./data/certbotSymfony/www:/var/www/certbot/:rw
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
web:
image: odoo:13.0
depends_on:
- db
ports:
- "8069:8069/tcp"
volumes:
- web-data:/var/lib/odoo
- ./data/odoo/config:/etc/odoo
- ./data/odoo/addons:/mnt/extra-addons

Is port 443 working for gRPC running behind nginx?

I setup a test environment to place docker, nginx before grpc server. And below are my configurations
docker-compose
version: '3.8'
services:
web:
build: .
command: gunicorn --timeout 100 --workers 2 --threads 4 django_root.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/public/django_root/static
expose:
- 8000
env_file:
- ./.env.dev
grpc:
build: .
command: python manage.py grpcrunserver 0.0.0.0:50051
env_file:
- ./.env.dev
nginx:
build:
context: ./nginx
dockerfile: Dockerfile-secure
volumes:
- static_volume:/public/django_root/static
ports:
- 1337:80
- 443:50052
depends_on:
- web
- grpc
volumes:
static_volume:
Dockerfile-secure
FROM nginx:1.19.0-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx-secure.conf /etc/nginx/conf.d
nginx-secure.conf
upstream django_root {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://django_root;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /public/django_root/static/;
}
}
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio"';
server {
listen 50052 ssl http2;
ssl_certificate /etc/nginx/server.crt;
ssl_certificate_key /etc/nginx/server.key;
access_log /var/log/nginx/a.log;
error_log /var/log/nginx/e.log;
location / {
grpc_pass grpc://grpc:50051;
}
}
The problem I hit is port 443 not working as I setup above in docker-compose file, but if I replace it with 8443, then my client can talk with grpc server. The error I can see from my client for port 443 use
case is below
E0211 15:08:05.178000000 22572 src/core/tsi/ssl_transport_security.cc:1439] Handshake failed with fatal error SSL_ERROR_SSL: error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED.
I use self-signed certificate for this test environment on localhost, could this be the problem?
I do not see 443 been disallowed for this case in neither nginx site or docker site. Need help on this, and in case 443 not allowed for this case, please refer me to the document.
Turns out it's certificate itself. Replacing self-signed certificate with let'sencrypt one and deploy to aws makes port 443 working.

Worker Tiemout Upstream Gunicorn on Docker (Bad Gateway 502)

I'm trying to run a platform building on Django, Docker, Nginx and Gunicorn from my Ubuntu server.
Before to ask you, i'm reading about my problem and i did on my nginx.conf:
location / {
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
...
}
Then, on my Gunicorn settings:
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-t 90", "config.wsgi:application"]
The problem persists and server always returns: 502 Bad Gateway. When i try to access to:
http://34.69.240.210:8000/admin/
From browser, the server redirect to
http://34.69.240.210:8000/admin/login/?next=/admin/
But show the error:
My Dockerfile:
FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY . /code/
RUN pip install -r requirements.txt
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-t 90", "config.wsgi:application"]
My docker-compose.yml:
version: "3.8"
services:
django_app:
build: .
volumes:
- static:/code/static
- .:/code
nginx:
image: nginx:1.15
ports:
- 8000:8000
volumes:
- ./config/nginx/conf.d:/etc/nginx/conf.d
- static:/code/static
depends_on:
- django_app
volumes:
.:
static:
My Nginx file:
upstream django_server {
server django_app:8000 fail_timeout=0;
}
server {
listen 8000;
server_name 34.69.240.210;
keepalive_timeout 5;
client_max_body_size 4G;
location / {
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_pass http://django_server;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Any idea what can i do to fix it?
Thank you.
Well, for the record.
My problem was the database connection. My Docker container couldn't connect to potsgres local database.
So, i added this line to postgresql.conf:
listen_addresses = '*'
Then, i added this line to pg_hba.conf
host all all 0.0.0.0/0 md5
Restart Postgres:
sudo service postgresql restart
My host postgres ip:
172.17.0.1
Test from inside docker:
psql -U myuser -d databasename -h 172.17.0.1 -W
Done! :)

Nginx Rverse Proxy to Tomcat (simple yes….? NO)

I have created a docker-compose file to spin up both an nginx and tomcat image. I use volumised files such /etc/nginx/nginx.conf and /etc/nginx/conf.d/app.conf
Same for Tomcat but with xml config files and webapps.
Both spin up and run fine… on their own. I can browse to Nginx and get the welcom page and the same for Tomcat on their respective ports, 81/8080.
However I cannot proxy the request to the backend tomcat. I’ll admit, I’m Apache and have been for years but I need to experiment.
My nginx.conf hasnt changed, its still default. I have an app.conf for the tomcat application (below). I do try and CMD mv the default.conf in teh tomcat Dockerfile but it still remains along side my app.conf so that maybe causing the issue?
my app.conf config is here: (apologies, couldnt get the code to output properly)
"server {
listen *:81;
set $allowOriginSite *;
proxy_pass_request_headers on;
proxy_pass_header Set-Cookie;
access_log /var/log/nginx/access.log combined;
error_log /var/log/nginx/error.log error;
# Upload size unlimited
client_max_body_size 0;
location /evf {
proxy_pass http://tomcat:8080;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
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_pass_header Set-Cookie;
}
}
tomcat:8080 being the name of the service in my docker-compose file.
Any help would be appreciated!
Thank you,
Craig
docker-compose.yml for reference;
version: '3'
services:
nginx:
build: ./nginx
image: nginx:evf
command: nginx -g "daemon off;"
networks:
- evf
container_name: evf-nginx
volumes:
- ./volumes/config/nginx-evf.conf:/etc/nginx/conf.d/nginx-evf.conf
- ./volumes/config/default.conf.disabled:/etc/nginx/conf.d/default.conf.disabled
ports:
- "81:80"
tomcat:
image: tomcat
working_dir: /usr/local/tomcat
volumes:
- ./volumes/config/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml
- ./volumes/webapps/EVF.war:/usr/local/tomcat/webapps/EVF.war
networks:
- evf
container_name: evf-tomcat
ports:
- "8080:8080" #expose 8080 externally to test connectivity.
networks:
evf:
Thanks,
In your nginx conf you have listen *:81 but you are exposing port 80 with "81:80".
So eiter expose port 81 with "81:81" or change you nginx config to listen *:80.
If the second option does not work try to replace the original nginx config by changing the volume file in your docker-compose.yml:
volumes:
- ./nginx/nginx-evf.conf:/etc/nginx/conf.d/default.conf

Docker nginx cannot reverse proxy within default network

I'm having problems with nginx reverse proxying as a docker container. My question is about how to correctly proxy pass nginx in a default docker network?
Here's my docker-compose.yml (unnecessary details omitted for brevity)
version: '3'
networks:
nginx_default:
external: true
services:
db:
image: postgres:10.2
ports:
- "5432:5432"
environment: ...
postgrest:
image: postgrest/postgrest
ports:
- "3000:3000"
environment: ...
nginx:
restart: always
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/sites-enabled/ruler
command: [nginx-debug, '-g', 'daemon off;']
webapp:
build:
context: "./"
dockerfile: Dockerfile.dev
volumes: ...
ports:
- "3001:3001"
environment: ...
Here's my nginx.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name _;
gzip on;
gzip_proxied any;
gzip_types text/plain text/xml text/css application/x-javascript;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
location / {
try_files $uri #node;
}
location /api/ {
try_files $uri #postgrest;
}
location #node {
proxy_pass http://webapp:3001;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_cache_control;
add_header X-Proxy-Cache $upstream_cache_status;
}
location #postgrest {
proxy_pass http://postgrest:3000;
proxy_http_version 1.1;
default_type application/json;
proxy_set_header Connection "";
proxy_hide_header Content-Location;
add_header Content-Location /api/$upstream_http_content_location;
}
}
And my Dockerfile.dev
FROM node:8.9
WORKDIR /client
CMD npm run dev -- -p 3001
When I do $ docker-compose up -d everything starts without an error. After that I can successfully do $ curl http://127.0.0.1:3001/ (webapp) and $ curl http://127.0.0.1:3000 (postgrest).
But when I try $ curl http://127.0.0.1:8080/ (nginx should handle here the proxying) I get default nginx welcome page. Also $ curl http://127.0.0.1:8080/api/ is not hitting the API :/
What may be the cause? Using $ docker inspect I see that every container is in the same default network.
Edit: Using $ docker-compose logs seems like the default network is not used at all O_o
docker-compose logs
WARNING: Some networks were defined but are not used by any service: nginx_default
Attaching to ruler_webapp_1, ruler_nginx_1
webapp_1 |
webapp_1 | > ruler# dev /client
webapp_1 | > next "-p" "3001"
webapp_1 |
webapp_1 | > Using external babel configuration
webapp_1 | > Location: "/client/.babelrc"
webapp_1 | DONE Compiled successfully in 1741ms09:04:49
webapp_1 |
My guess is you mapped your local nginx configuration file to the wrong file on the container side. The default configuration file for the nginx image is located at /etc/nginx/conf.d/default.conf so the volume of the nginx container should be:
./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
You can check your configuration file is used correctly by executing:
docker-compose exec nginx nginx -T
Side notes:
Never use the latest tag, because in some time you may face broken compatibility issues. Use fixed version tag 1, 1.13 etc. instead
You don't need to publish ports everywhere, eg. 3000:3000, 3001:3001. Those ports will be accessible internally by containers
Your config is a partial config and not a complete nginx config. So it needs to go inside conf.d inside the container and not on nginx.conf or sites-enabled. So change
volumes:
- ./nginx/nginx.conf:/etc/nginx/sites-enabled/ruler
to
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
And now it should start working

Resources