Setup docker services with subdomains and ssl

dor yosef
4 min readDec 25, 2020

Would it will be great if you could run all your docker services like: home-assistant, nextcloud, plex etc. on docker-compose and each service will have its own subdomain.

What will be done

Configure docker services with reverse proxy.
Each service will get subdomain like: serviceX.example.duckdns.org.
SAN ssl.

For the demonstration I will use home-assistant and nextcloud docker images.

Prerequisite

  • ubuntu
  • sudo user
  • docker and certbot install
  • port 80 and 443 open
  • duckdns configure to your ip

checking connectivity

Make sure port 80 is open, run the following command:

docker run -d — rm — name web-test -p 80:8000 crccheck/hello-world

in your web browser navigate to your dns, you should see:

Make sure port 443 is open as well, we will need it after ssl certification is finished.

Now remove the container with:

docker rm -f web-test

Let’s certificate

We want to create wild card certification for our subdomains

Service1.example.duckdns.org
service2.example.duckdns.org

For this we will need to use let’sEncrypt dns challange

Run the following command:

sudo certbot certonly --manual --preferred-challenges=dns --email email@example.com --agree-tos  --manual-public-ip-logging-ok -d "*.example.duckdns.org"

the output should be like:

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.duckdns.org/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.duckdns.org/privkey.pem
Your cert will expire on 2021-03-24. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

Now run:

sudo certbot certificates

The output should be like:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: example.duckdns.org
Serial Number: 41ba80479f10c3cb611167c04f7ac39f111
Domains: *.example.duckdns.org
Expiry Date: 2021-03-24 20:38:48+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/example.duckdns.org/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.duckdns.org/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

notice the * at the beginning of the dns

Preparing nginx files

Create services folder that will be use for our docker-compose and go inside

mkdir example
cd example

Now lets prepare our nginx reverse proxy files

mkdir rproxy
cd rproxy

Create configuration folder:

mkdir conf.d
cd conf.d

Create the following files:

redirect.conf

server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}

common.conf

add_header Strict-Transport-Security    "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

common_location.conf

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;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_http_version 1.1;

ssl.conf

ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
ssl_ecdh_curve secp384r1;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384 OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/cert/dhparams.pem;
ssl_certificate /etc/nginx/ssl/live/example.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/example.duckdns.org/privkey.pem;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

Change /etc/nginx/ssl/live/example.duckdns.org/ to your certificate location.

nextcloud.conf

upstream nextcloud {
server nextcloud:80;
}

server {
listen 443 ssl;
server_name nextcloud.example.duckdns.org;

include /etc/nginx/conf.d/common.conf;
include /etc/nginx/conf.d/ssl.conf;
client_max_body_size 4G; location / {
proxy_pass http://nextcloud;
include /etc/nginx/conf.d/common_location.conf;
}
}

hass.conf

upstream hass {
server 172.17.0.1:8123;
}
server {
listen 443 ssl;
server_name hass.example.duckdns.org;
include /etc/nginx/conf.d/common.conf;
include /etc/nginx/conf.d/ssl.conf;
location / {
proxy_pass http://hass;
include /etc/nginx/conf.d/common_location.conf;
}
}

home-assistant service will run on the host network so we will be able to communicate with all the other IOT we have at the same network.
This is way we use here docker bridge ip 172.17.0.1.
To communicate between the host and the docker network.

Now lets create dhparams file to make it more secure.

cd ..
mkdir dhparams
openssl dhparam -out dhparams/dhparams.pem 4096

creating .pem file will take sometime.

creating docker-compose file

nano docker-compose.yml

version: '3.8'

services:
proxy:
image: 'nginx:1.19.6-alpine'
volumes:
- './rproxy/conf.d:/etc/nginx/conf.d'
- './rproxy/dhparams:/etc/nginx/cert'
- '/etc/letsencrypt:/etc/nginx/ssl'
- '/etc/localtime:/etc/localtime:ro'
ports:
- '80:80'
- '443:443'
restart: always
#---------nginx end-----------#
db:
image: mariadb
volumes:
- 'db:/var/lib/mysql'
- '/etc/localtime:/etc/localtime:ro'
environment:
- MYSQL_ROOT_PASSWORD=rootPass
- MYSQL_PASSWORD=myPass
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
restart: unless-stopped
#---------mariadb end-----------#
nextcloud:
image: 'nextcloud:stable'
depends_on:
- proxy
- db
volumes:
- 'nextcloud:/var/www/html'
- './nextcloud/config:/var/www/html/config'
- './nextcloud/custom_apps:/var/www/html/custom_apps'
- './nextcloud/data:/var/www/html/data'
- './nextcloud/themes:/var/www/html/themes'
- '/etc/localtime:/etc/localtime:ro'
restart: unless-stopped
#--------nextcloud end------------#
hass:
image: 'homeassistant/home-assistant:stable'
depends_on:
- proxy
volumes:
- './hass/config:/config'
- '/etc/localtime:/etc/localtime:ro'
network_mode: host
restart: unless-stopped
#--------hass end------------#
#------------------services end---------------------#
volumes:
nextcloud:
db:
#------------------volumes end---------------------#

Folder tree

.
├── docker-compose.yml
└── rproxy
├── conf.d
│ ├── common.conf
│ ├── common_location.conf
│ ├── hass.conf
│ ├── nextcloud.conf
│ ├── redirect.conf
│ └── ssl.conf
└── dhparams
└── dhparams.pem

start docker-compose with:

docker-compose up -d

How does it work?

  • The dns configure to your ip address
  • The router forward port 80 and 443 to the ubuntu machine
  • Port 80 and 443 are open for nginx docker service
  • nextcloud service communicate on dokcer network with service name as hostname.

For example if we will add the hello-world docker image from above to our docker-compose.yaml:

web:
image: crccheck/hello-world

Connect to nginx container with
docker container exec -it <container_name> bash
and run curl http://web:8000 we will get the hello world message.

Back to our configuration

  • home-assistant service communicate on the host network and need docker bridge to communicate with nginx so we use special ip 172.17.0.1

when request arrived to nextcloud.example.duckdns.org nginx redirect to https and pass the request to http://nextcloud which exist on the docker local network

renewal certification should be automatic as certbot create cronjob under /etc/cron.d/certbot

--

--