Created
December 16, 2020 00:56
-
-
Save igortrinidad/c0293f2a79ce55c9abe32cf6da2cb2ed to your computer and use it in GitHub Desktop.
CEE Docker setup with postgres, redis, nginx e certbot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# /docker-volumes/nginx/app.conf | |
server { | |
listen 80; | |
server_name cee.igortrindade.dev; | |
server_tokens off; | |
location /.well-known/acme-challenge/ { | |
root /var/www/certbot; | |
} | |
location / { | |
return 301 https://$host$request_uri; | |
} | |
} | |
server { | |
listen 443 ssl http2; | |
listen [::]:443 ssl http2; | |
server_name cee.igortrindade.dev; | |
root /home/cee/api/public; | |
ssl_certificate /etc/letsencrypt/live/cee.igortrindade.dev/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/cee.igortrindade.dev/privkey.pem; | |
include /etc/letsencrypt/options-ssl-nginx.conf; | |
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | |
location = /favicon.ico { access_log off; log_not_found off; } | |
location = /robots.txt { access_log off; log_not_found off; } | |
add_header X-Frame-Options "SAMEORIGIN"; | |
add_header X-XSS-Protection "1; mode=block"; | |
add_header X-Content-Type-Options "nosniff"; | |
client_max_body_size 9M; | |
charset utf-8; | |
access_log off; | |
error_log /var/log/nginx/cee.igortrindade.dev.log error; | |
location / { | |
proxy_pass http://cee_app:3333; | |
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; | |
} | |
location ~ /\.(?!well-known).* { | |
deny all; | |
} | |
location ~* \.(js|css|png|jpg|jpeg|gif|ico|mp3|svg)$ { | |
expires 365d; | |
add_header Cache-Control "public, no-transform"; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: "3.4" | |
services: | |
app: | |
build: . | |
container_name: cee_app | |
ports: | |
- "3333:3333" | |
environment: | |
HOST: '0.0.0.0' | |
DB_HOST: db | |
REDIS_HOST: redis | |
volumes: | |
- .:/app | |
networks: | |
- network_cee | |
db: | |
container_name: cee_pg | |
image: postgres | |
restart: unless-stopped | |
ports: | |
- "5432:5432" | |
volumes: | |
- ./docker-volumes/postgres:/var/lib/postgresql/data | |
environment: | |
POSTGRES_DB: cee | |
POSTGRES_PASSWORD: 123456 | |
networks: | |
- network_cee | |
redis: | |
container_name: cee_redis | |
image: redis | |
restart: unless-stopped | |
ports: | |
- "6379:6379" | |
networks: | |
- network_cee | |
nginx: | |
container_name: cee_nginx | |
image: nginx | |
restart: unless-stopped | |
volumes: | |
- ./docker-volumes/nginx:/etc/nginx/conf.d | |
- ./docker-volumes/certbot/conf:/etc/letsencrypt | |
- ./docker-volumes/certbot/www:/var/www/certbot | |
- ./docker-volumes/log/nginx:/var/log/nginx | |
ports: | |
- "80:80" | |
- "443:443" | |
environment: | |
HOST: '0.0.0.0' | |
networks: | |
- network_cee | |
depends_on: | |
- app | |
- db | |
- redis | |
- certbot | |
healthcheck: | |
test: ["CMD", "curl", "-f", "http://localhost:3333"] | |
interval: 30s | |
timeout: 20s | |
retries: 10 | |
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" | |
certbot: | |
container_name: cee_certbot | |
image: certbot/certbot | |
restart: unless-stopped | |
volumes: | |
- ./docker-volumes/certbot/conf:/etc/letsencrypt | |
- ./docker-volumes/certbot/www:/var/www/certbot | |
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" | |
networks: | |
network_cee: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM node:13.7.0 | |
USER root | |
WORKDIR /app | |
COPY package*.json ./ | |
RUN npm install | |
COPY . . | |
CMD ["npm", "start"] | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
if ! [ -x "$(command -v docker-compose)" ]; then | |
echo 'Error: docker-compose is not installed.' >&2 | |
exit 1 | |
fi | |
domains=(cee.igortrindade.dev www.cee.igortrindade.dev) | |
rsa_key_size=4096 | |
data_path="./docker-volumes/certbot" | |
email="" # Adding a valid address is strongly recommended | |
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits | |
if [ -d "$data_path" ]; then | |
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision | |
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then | |
exit | |
fi | |
fi | |
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then | |
echo "### Downloading recommended TLS parameters ..." | |
mkdir -p "$data_path/conf" | |
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" | |
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem" | |
echo | |
fi | |
echo "### Creating dummy certificate for $domains ..." | |
path="/etc/letsencrypt/live/$domains" | |
mkdir -p "$data_path/conf/live/$domains" | |
docker-compose run --rm --entrypoint "\ | |
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\ | |
-keyout '$path/privkey.pem' \ | |
-out '$path/fullchain.pem' \ | |
-subj '/CN=localhost'" certbot | |
echo | |
echo "### Starting nginx ..." | |
docker-compose up --force-recreate -d nginx | |
echo | |
echo "### Deleting dummy certificate for $domains ..." | |
docker-compose run --rm --entrypoint "\ | |
rm -Rf /etc/letsencrypt/live/$domains && \ | |
rm -Rf /etc/letsencrypt/archive/$domains && \ | |
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot | |
echo | |
echo "### Requesting Let's Encrypt certificate for $domains ..." | |
#Join $domains to -d args | |
domain_args="" | |
for domain in "${domains[@]}"; do | |
domain_args="$domain_args -d $domain" | |
done | |
# Select appropriate email arg | |
case "$email" in | |
"") email_arg="--register-unsafely-without-email" ;; | |
*) email_arg="--email $email" ;; | |
esac | |
# Enable staging mode if needed | |
if [ $staging != "0" ]; then staging_arg="--staging"; fi | |
docker-compose run --rm --entrypoint "\ | |
certbot certonly --webroot -w /var/www/certbot \ | |
$staging_arg \ | |
$email_arg \ | |
$domain_args \ | |
--rsa-key-size $rsa_key_size \ | |
--agree-tos \ | |
--force-renewal" certbot | |
echo | |
echo "### Reloading nginx ..." | |
docker-compose exec nginx nginx -s reload |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The other day, I wanted to quickly launch an nginx server with Let’s Encrypt certificates. I expected the task to be easy and straightforward. Turns out: I was wrong, it took a significant amount of time and it’s quite a bit more complicated. | |
Of course, in the grand scheme of things, it is pretty straightforward. But there are a couple of details you need to be aware of. The goal of this guide is to help you build a docker-compose setup that runs nginx in one container and a service for obtaining and renewing HTTPS certificates in another. Whether you’re using nginx as a proxy for your web app or just for serving static files, this guide is for you. | |
TL;DR: The full code from this guide is available on GitHub. | |
Quick Reminder: What is docker-compose? | |
docker-compose is a tool for defining containers and running them. It’s a great choice when you have multiple interdependent containers but you don’t need a full-blown container cluster like Kubernetes. | |
The official documentation puts it like this: | |
With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration | |
This guide requires docker-compose. If you don’t have it yet, take a look at the installation instructions and get it. | |
Hint: If you’re installing docker-compose on CoreOS, it needs to go into /opt/bin instead of /usr/local/bin. | |
The Setup | |
Official images of nginx and an automated build of certbot, the EFF’s tool for obtaining Let’s Encrypt certificates, are available in the Docker library. | |
Let’s begin with a basic docker-compose.yml configuration file that defines containers for both images: | |
version: '3' | |
services: | |
nginx: | |
image: nginx:1.15-alpine | |
ports: | |
- "80:80" | |
- "443:443" | |
volumes: | |
- ./data/nginx:/etc/nginx/conf.d | |
certbot: | |
image: certbot/certbot | |
Here is a simple nginx configuration that redirects all requests to HTTPS. The second server definition sets up a proxy to example.org for demonstration purposes. This is where you would add your own configuration for proxying requests to your app or serving local files. | |
Save this file as data/nginx/app.conf alongside docker-compose.yml. Change example.org in both occurrences of server_name to your domain name. | |
server { | |
listen 80; | |
server_name example.org; | |
location / { | |
return 301 https://$host$request_uri; | |
} | |
} | |
server { | |
listen 443 ssl; | |
server_name example.org; | |
location / { | |
proxy_pass http://example.org; #for demo purposes | |
} | |
} | |
If you would try to run docker-compose up now, nginx would fail to start because there is no certificate. We need to make some adjustments. | |
Linking up nginx and certbot | |
Let’s Encrypt performs domain validation by requesting a well-known URL from a domain. If it receives a certain response (the “challenge”), the domain is considered validated. This is similar to how Google Search Console establishes ownership of a website. The response data is provided by certbot, so we need a way for the nginx container to serve files from certbot. | |
First of all, we need two shared Docker volumes. One for the validation challenges, the other for the actual certificates. | |
Add this to the volumes list of the nginx section in docker-compose.yml. | |
- ./data/certbot/conf:/etc/letsencrypt | |
- ./data/certbot/www:/var/www/certbot | |
And this is the counterpart that needs to go in the certbot section: | |
volumes: | |
- ./data/certbot/conf:/etc/letsencrypt | |
- ./data/certbot/www:/var/www/certbot | |
Now we can make nginx serve the challenge files from certbot! Add this to the first (port 80) section of our nginx configuration (data/nginx/app.conf): | |
location /.well-known/acme-challenge/ { | |
root /var/www/certbot; | |
} | |
After that, we need to reference the HTTPS certificates. Add the soon-to-be-created certificate and its private key to the second server section (port 443). Make sure to once again replace example.org with your domain name. | |
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem; | |
And while we’re at it: The folks at Let’s Encrypt maintain best-practice HTTPS configurations for nginx. Let’s also add them to our config file. This will score you a straight A in the SSL Labs test! | |
include /etc/letsencrypt/options-ssl-nginx.conf; | |
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; | |
The Chicken or the Egg? | |
Now for the tricky part. We need nginx to perform the Let’s Encrypt validation But nginx won’t start if the certificates are missing. | |
So what do we do? Create a dummy certificate, start nginx, delete the dummy and request the real certificates. | |
Luckily, you don’t have to do all this manually, I have created a convenient script for this. | |
Download the script to your working directory as init-letsencrypt.sh: | |
curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh | |
Edit the script to add in your domain(s) and your email address. If you’ve changed the directories of the shared Docker volumes, make sure you also adjust the data_path variable as well. | |
Then run chmod +x init-letsencrypt.sh and sudo ./init-letsencrypt.sh. | |
Automatic Certificate Renewal | |
Last but not least, we need to make sure our certificate is renewed when it’s about to expire. The certbot image doesn’t do that automatically but we can change that! | |
Add the following to the certbot section of docker-compose.yml: | |
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" | |
This will check if your certificate is up for renewal every 12 hours as recommended by Let’s Encrypt. | |
In the nginx section, you need to make sure that nginx reloads the newly obtained certificates: | |
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" | |
This makes nginx reload its configuration (and certificates) every six hours in the background and launches nginx in the foreground. | |
Docker-compose Me Up! | |
Everything is in place now. The initial certificates have been obtained and our containers are ready to launch. Simply run docker-compose up and enjoy your HTTPS-secured website or app. | |
Did you find this guide helpful? Make sure to subscribe to my newsletter so you don’t miss the next article with useful deployment tips! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment