The goal of this this tutorial is to fully deploy a VM with multiple apps.
All the apps data will be stored in Docker volumes mounted to the host at /var/www/my-deploys
for easy edit and backup. The benefit of this is you can create a GitHub repository and store all your configuration files in one place.
To deploy the docker-compose.yml
of the apps below into Portainer, go to Stacks
> Add stack
.
Inspired from: https://mindsers.blog/en/post/https-using-nginx-certbot-docker/
We will run NGINX inside a Docker container, and configure our hosts on the VM at /var/www/my-deploys/nginx/
.
mkdir -p /var/www/my-deploys/
Stack Name: nginx
version: '3'
services:
webserver:
image: nginx:latest
user: '$UID:$GID'
ports:
- 80:80
- 443:443
network_mode: host
restart: always
volumes:
- /var/www/my-deploys/nginx/nginx.conf:/etc/nginx/nginx.conf
- /var/www/my-deploys/nginx/conf.d/:/etc/nginx/conf.d/
- /var/www/my-deploys/nginx/ssl/:/etc/nginx/ssl/
- /var/www/my-deploys/nginx/.well-known/:/usr/share/nginx/html/.well-known/
Update the NGINX configuration to include our mounted directory configurations.
sudo nano /var/www/my-deploys/nginx/nginx.conf
user nginx;
worker_processes auto;
include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
##
# Basic Settings
##
sendfile on;
# tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
server_names_hash_bucket_size 128;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
sudo nano /var/www/my-deploys/nginx/conf.d/default.conf
server {
listen 80;
listen [::]:80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
docker exec nginx-webserver-1 nginx -s reload
When deployed, you should be able to reach you NGINX web server on port 80 and see the NGINX hello page.
For all NGINX configurations, you can use 127.0.0.1
because we specified network_mode: host
which make the network of the container the same as the host. We are trying to achieve portability, not full isolation.
We will configure a service running locally on the VM on port 49500
to be accessible externally at url diff.rigwild.dev
.
Simply replace all diff.rigwild.dev
with your own domain.
You can either follow the tutorial below or run source 2_add_nginx_service.sh
script.
sudo nano /var/www/my-deploys/nginx/conf.d/diff.rigwild.dev.conf
upstream diff.rigwild.dev {
server 127.0.0.1:49500;
keepalive 64;
}
server {
## tls off ## if ($host = diff.rigwild.dev) {
## tls off ## return 301 https://$host$request_uri;
## tls off ## }
listen 80;
listen [::]:80;
server_name diff.rigwild.dev;
location ^~ /.well-known/ {
alias /usr/share/nginx/html/.well-known/;
}
## tls off ## return 404;
}
## tls off ## server {
## tls off ## listen 443 ssl;
## tls off ## listen [::]:443 ssl;
## tls off ## server_name diff.rigwild.dev;
## tls off ##
## tls off ## location / {
## tls off ## proxy_set_header X-Forwarded-Host $host;
## tls off ## proxy_set_header X-Forwarded-Server $host;
## tls off ## proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
## tls off ## proxy_pass http://diff.rigwild.dev;
## tls off ## proxy_http_version 1.1;
## tls off ## proxy_pass_request_headers on;
## tls off ## proxy_set_header Connection "keep-alive";
## tls off ## proxy_store off;
## tls off ## }
## tls off ##
## tls off ## ssl_certificate /etc/nginx/ssl/live/diff.rigwild.dev/fullchain.pem;
## tls off ## ssl_certificate_key /etc/nginx/ssl/live/diff.rigwild.dev/privkey.pem;
## tls off ## }
To test if it works, you need to reload the NGINX configuration, you can do either:
- In Portainer, go to
Stacks
>nginx
>nginx-webserver-1
>Restart
- Run (benefit: zero downtime!)
docker exec nginx-webserver-1 nginx -s reload
You should be able to access your service.
curl diff.rigwild.dev
<!DOCTYPE html>
<html lang="en">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/login?next=/">/login?next=/</a>. If not, click the link.</p>
</html>
Now let's configure HTTPS/TLS for our domain using the Let's Encrypt ACME. Let's first do a dry run to make sure our setup is working as expected.
sudo certbot certonly --webroot --webroot-path /var/www/my-deploys/nginx/ -d diff.rigwild.dev --dry-run
If it was successfuly, you can now request your certificate!
sudo certbot certonly --webroot --webroot-path /var/www/my-deploys/nginx/ -d diff.rigwild.dev
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for diff.rigwild.dev
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/diff.rigwild.dev/fullchain.pem
Key is saved at: /etc/letsencrypt/live/diff.rigwild.dev/privkey.pem
This certificate expires on 2024-02-24.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 that we got the certificates, we can move them to the Docker volume shared with the NGINX container, then enable the TLS configuration.
sudo mkdir -p /var/www/my-deploys/nginx/ssl/live/
sudo mkdir -p /var/www/my-deploys/nginx/ssl/archive/
sudo cp -R /etc/letsencrypt/archive/diff.rigwild.dev /var/www/my-deploys/nginx/ssl/archive/
sudo cp -R /etc/letsencrypt/live/diff.rigwild.dev /var/www/my-deploys/nginx/ssl/live/
sudo cp /var/www/my-deploys/nginx/conf.d/diff.rigwild.dev.conf /var/www/my-deploys/nginx/conf.d/diff.rigwild.dev.conf.old
sudo sed -i -e 's/## tls off ## //g' /var/www/my-deploys/nginx/conf.d/diff.rigwild.dev.conf
docker exec nginx-webserver-1 nginx -s reload
Done! The service is not accessible at https://diff.rigwild.dev/, and insecure requests http
will be redirected to secure https
.
curl http://diff.rigwild.dev/
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>
Stack name: monitoring
version: '3.2'
services:
prometheus:
image: prom/prometheus
user: '$UID:$GID'
command:
- '--config.file=/opt/prometheus/prometheus.yml'
- '--web.listen-address=127.0.0.1:9090'
- '--storage.tsdb.retention.time=1y'
- '--storage.tsdb.path=/opt/prometheus/data'
ports:
- 9090:9090
network_mode: host
volumes:
- /var/www/my-deploys/prometheus/prometheus.yml:/opt/prometheus/prometheus.yml
- /var/www/my-deploys/prometheus/data/:/opt/prometheus/data/
prometheus-pushgateway:
image: prom/pushgateway
command:
- '--web.listen-address=127.0.0.1:9091'
- '--web.external-url=https://prometheus-pushgateway.rigwild.dev'
network_mode: host
ports:
- 9091:9091
grafana:
image: grafana/grafana-oss
user: '$UID:$GID'
container_name: grafana
restart: unless-stopped
network_mode: host
ports:
- '9099:9099'
volumes:
- /var/www/my-deploys/grafana/data/:/var/lib/grafana/
- /var/www/my-deploys/grafana/defaults.ini:/usr/share/grafana/conf/defaults.ini
Prometheus configuration:
sudo mkdir /var/www/my-deploys/prometheus/
sudo chown -R 65534:65534 /var/www/my-deploys/prometheus/data
sudo nano /var/www/my-deploys/prometheus/prometheus.yml
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9091', 'localhost:9090']
Now simply add your pushgateway URL to your NGINX configuration, see Add a service to NGINX.
Grafana configuration:
sudo mkdir /var/www/my-deploys/grafana/
sudo chown -R 65534:65534 /var/www/my-deploys/grafana/
sudo wget https://raw.githubusercontent.com/grafana/grafana/main/conf/defaults.ini -O /var/www/my-deploys/grafana/defaults.ini
sudo sed -i 's/^http_addr =.*$/http_addr = 127.0.0.1/g' /var/www/my-deploys/grafana/defaults.ini
sudo sed -i 's/^http_port =.*$/http_port = 9099/g' /var/www/my-deploys/grafana/defaults.ini
sudo sed -i 's/^enforce_domain =.*$/enforce_domain = true/g' /var/www/my-deploys/grafana/defaults.ini
sudo sed -i 's/^domain =.*$/domain = grafana.rigwild.dev/g' /var/www/my-deploys/grafana/defaults.ini
Now simply add your grafana URL to your NGINX configuration, see Add a service to NGINX.