GCP provides docker optimised compute nodes where you can spin up a very light weight (and minimal footprint) containers. They run this on the so called Container Optimised OS and seems to work great for simple use cases. However, there is a limitation: You can only auto-config it to run 1 single container at a time. The OS takes care of re-starting, port mapping etc, but this limitation sometimes makes it tough to run your hobby projects.
Lets try to go over a use case here. For this example, we take a project – Metabase, which runs a Jetty app server on port 3000.
Use Case
- You have your primary docker service (Metabase) running on port 3000.
- You want to run a nginx proxy on 80/443.
- You want to setup a certbot + LetsEncrypt SSL certificate on this server as well, so you have your service securely out in the open.
Prerequisites
- You have a GCP Compute node up with Metabase, and is running on port 3000. You can follow this tutorial here, in case you run into trouble.
- You have assigned a public IP to the node, and now your setup is accessible at
http://your-public-ip:3000
Setup
At this point, you have a metabase container running on your GCP machine. You can login via SSH, or use the in-browser console service to see this using docker ps
inside your compute node. Its time to setup the nginx reverse proxy at this point.
Starting with the basics, we need some nginx configurations. This config does two things:
- Start the nginx server on port 80/443. For this example, we use the host network itself, since GCP uses the host network when running containers.
- Point the certbot challenge for laters correctly to get your certs.
user_name@gcpnode $ mkdir nginx
user_name@gcpnode $ nano nginx/nginx.conf
Now, setting up the nginx/nginx.conf
is going to be easy. Lets see how it should look:
events {
worker_connections 4096; ## Default: 1024
}
http {
log_format combined_ssl '$remote_addr - $remote_user [$time_local] '
'$ssl_protocol/$ssl_cipher '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
server {
listen 80;
server_name subdomain.domain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
}
Now, that would be enough to serve the certbot
challenges. Lets run the nginx
server now. If it complaints about missing folders, just create it.
user_name@gcpnode $ docker run --network host -p 80:80 -p 443:443 -v
/home/user_name/nginx/nginx.conf:/etc/nginx/nginx.conf -v
/home/user_name/certbot/letsencrypt:/etc/letsencrypt -v
/home/user_name/certbot/www:/var/www/certbot -d nginx
Now check if nginx is running correctly, using docker ps
. If things are good, its time to run the certbot challenge.
user_name@gcpnode $ docker run --rm --name temp_certbot -v
/home/user_name/certbot/letsencrypt:/etc/letsencrypt -v
/home/user_name/certbot/www:/tmp/letsencrypt -v /home/user_name/servers-
data/certbot/log:/var/log certbot/certbot:v1.8.0 certonly --webroot --
agree-tos --renew-by-default --preferred-challenges http-01 --server
https://acme-v02.api.letsencrypt.org/directory --text --email
useremail@domain.com -w /tmp/letsencrypt -d subdomain.domain.com
There we go. If things go alright, you should have your certs in /home/user_name/certbot/letsencrypt
.
Now that this is done, time to route some SSL traffic to your webserver. Lets edit the nginx config one more time, and add the necessary routes to your web-server. In this case, its a metabase running on the same machine with an HTTP out on port 3000.
Lets update the nginx
config in nginx/nginx.conf
again to route the reverse proxied traffic to your metabase
installation.
events {
worker_connections 4096; ## Default: 1024
}
http {
log_format combined_ssl '$remote_addr - $remote_user [$time_local] '
'$ssl_protocol/$ssl_cipher '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
server {
listen 80;
server_name subdomain.domain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name subdomain.domain.com;
access_log /var/log/nginx/access.log combined_ssl;
ssl_certificate /etc/letsencrypt/live/subdomain.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/subdomain.domain.com/privkey.pem;
location / {
set $upstream "site_upstream";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-Port $server_port;
proxy_set_header X-Real-Scheme $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on;
expires off;
proxy_pass http://$upstream;
}
}
upstream site_upstream{
server your-gcp-private-ip:3000;
}
}
Make sure to update the highlighted code lines to match your install. Now, once these are done, its time to restart the nginx container again to enjoy the new HTTPS service. You can do that by something like:
user_name@gcpnode $ docker ps # and, note the nginx container id.
user_name@gcpnode $ docker stop <container_id> # pass the right id.
user_name@gcpnode $ docker run --network host -p 80:80 -p 443:443 -v
/home/user_name/nginx/nginx.conf:/etc/nginx/nginx.conf -v
/home/user_name/certbot/letsencrypt:/etc/letsencrypt -v
/home/user_name/certbot/www:/var/www/certbot -d nginx
# Hopefully, things start alright here. Check docker logs for clarity.
user_name@gcpnode $ docker logs -f <container_id> # Use the new id.
Now you should have your metabase service running under subdomain.domain.com
. Things to think about:
- Docker optimized OS on GCP uses the host network by default. Probably running them on a different separate network should be the best way to do it. The port forwarding in this example is kind of discarded, since we use
network=host
- You should be pointing an A record in your DNS to point to the gcp-public-ip.
- You should not have to expose any ports outside the gcp firewall, other than HTTPS. Probably even block the HTTP access.
- Since metabase handles sensitive stuff, its always recommended to host it behind a secure access VPN/Cloudflare for teams access control.
Hope it helps.
You must be logged in to post a comment.