Installing odoo 16 with docker, docker-compose and lets-encrypt, on hetzner

This post reveals the series of steps I did to have relationalgames running Odoo on a production https server.

On Hetzner, you can just buy a 2 CPU server with 40Gb as a starting point.
As soon you have setup your hetzner server with your local ssh key, you can ssh root@[YOUR-IP] to enter the server.

Docker and docker-compose

Following steps at: How to install Docker and docker compose on Ubuntu
When running sudo apt-get update, got an error. To solve it needed to delete file docker.list at /etc/apt/sources.list.d (thanks to How to fix apt-get update error on Ubuntu or Debian)

Next, following steps at:

As this is ubuntu 22.04 and not 20.04 in step:
sudo add-apt-repository “deb [arch=amd64] Index of linux/ubuntu/ focal stable”
Replace “focal” with “jammy”

In the end the steps were a blend from the two links above

Docker and docker compose prerequisites

sudo apt-get install curl
sudo apt-get install gnupg
sudo apt-get install ca-certificates
sudo apt-get install lsb-release

Download the docker gpg file to Ubuntu

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

sudo apt install apt-transport-https ca-certificates curl software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository “deb [arch=amd64] Index of linux/ubuntu/ jammy stable”
apt-cache policy docker-ce
sudo apt-get update

Install docker and docker compose on Ubuntu

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Verify the Docker and docker compose install on Ubuntu

sudo docker run hello-world
sudo systemctl status docker

Odoo

Following steps at Docker for Odoo 16 - get Odoo within 1 minute

$ git clone GitHub - minhng92/odoo-16-docker-compose: Fast set-up Odoo 16 for dev/production with Docker Compose
$ cd odoo-16-docker-compose
$ sudo chmod -R 777 addons && sudo chmod -R 777 etc && mkdir -p postgresql && sudo chmod -R 777 postgresql
$ if grep -qF “fs.inotify.max_user_watches” /etc/sysctl.conf; then echo $(grep -F “fs.inotify.max_user_watches” /etc/sysctl.conf); else echo “fs.inotify.max_user_watches = 524288” | sudo tee -a /etc/sysctl.conf; fi
$ sudo sysctl -p # apply new config immediately

Then I copied my custom addons to the addons folder
Ex: git clone GitHub - diogocsc/carddecks: Playable cards and decks

Then changed admin password in etc/odoo.conf to my own pwd

And then continued

docker-compose up -d

Oops…docker-compose wasn’t installed after all.
So, installed it: sudo apt install docker-compose

And again:
docker-compose up -d

with this, your server should be up on http://[YOUR-IP]:8069

Do note that the port may be one of your choice, as setup in odoo.conf file in etc folder

Adding https with let’s encrypt

First, ensure your server has its A records setup, pointing to your server’s IP Address.

Following this instructions by letter didn’t work at first, because we were missing the upstream.
Also the https server redirect revealed not necessary.

See annex for both http and https final configurations.

Do note that for https to work, we first need to run certbot as indicated in the link, so the certificates are loaded to our local machine.

Annex

nginx/conf/default.conf file for http requests:

upstream odoo {
    server 65.21.245.173:8069;
}
upstream odoochat {
    server 65.21.245.173:8072;
}

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}


server {
    # listen [::]:80;
    listen 80;

    server_name relationalgames.com;

    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;

    # Add Headers for odoo proxy mode
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;

    # log
    access_log /var/log/nginx/odoo.access.log;
    error_log /var/log/nginx/odoo.error.log;
  
    # Redirect websocket requests to odoo gevent port
    location /websocket {
      proxy_pass http://odoochat;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
      
    }

    # Redirect requests to odoo backend server
    location / {
        proxy_redirect off;
        proxy_pass http://odoo;
    }

    location ~* /web/static/ {
      proxy_pass http://odoo;
    }

    # common gzip
    gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
    gzip on;

}

nginx.conf/default.conf file for https requests:

upstream odoo {
    server 65.21.245.173:8069;
}
upstream odoochat {
    server 65.21.245.173:8072;
}

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}
# line below is needed so website edits work properly.
add_header 'Content-Security-Policy' 'upgrade-insecure-requests';

server {
    listen [::]:80;
    listen 80;

    server_name relationalgames.com;

    return 301 https://www.relationalgames.com$request_uri;
    
}


server {
    listen [::]:443 ssl http2;
    listen 443 ssl http2;

    server_name relationalgames.com;
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    

    ssl_certificate /etc/nginx/ssl/live/relationalgames.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/relationalgames.com/privkey.pem;


    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/html;
    }


    # Add Headers for odoo proxy mode
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;

    # log
    access_log /var/log/nginx/odoo.access.log;
    error_log /var/log/nginx/odoo.error.log;
  
    # Redirect websocket requests to odoo gevent port
    location /websocket {
      proxy_pass http://odoochat;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Real-IP $remote_addr;
    }

    # Redirect requests to odoo backend server
    location / {
      proxy_pass http://odoo;
    }

    # common gzip
    gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
    gzip on;

    location ~* /web/static/ {
      proxy_pass http://odoo;
    }
}

To renew Let’s Encrypt certificate after following above steps we can, in our server bash run:

docker-compose restart certbot
docker-compose restart nginx

To update an app run, for example, after ssh to your server:

docker-compose restart

And then, go to the Odoo instance and update your apps manually one by one.
(This is feasible for not so often updates, and when there are just a couple of apps to update)
If you have more apps, you should think of updating odoo when relauching the server.

As i was refreshing the letsencrypt certificate manually every 90 days… I decided to try something new.
A little search online took me to https://www.baeldung.com/linux/schedule-script-execution

Parallel to this, i found that the sh file I had just created with the two docker restart commands here, had not execute permissions, so I needed to run ´chmod +x letsencrypt_renew.sh´ to be able to execute it. (thanks to https://www.cyberciti.biz/faq/run-execute-sh-shell-script/)

One other interesting possibility to only run timer at certain time is: https://stackoverflow.com/questions/704927/does-cron-expression-in-unix-linux-allow-specifying-exact-start-and-end-dates