Deploying Joplin Server on Docker

Some weeks ago I decided to move my notes from Microsoft OneNote to Joplin. Microsoft OneNote is a great tool for taking notes collaborative, but sometimes it drives me insane and I wanted a more portable form at for my notes.

Markdown is a perfect portable format, and it is widly adopted. I really like the idea behind Markdown, and I even supported a Microsoft User Voice to add native Markdown support into OneNote. So my new note taking tool had to support Markdown. Long story short: Joplin was my tool of choice. It’s running on Windows and there is also an iOS app. Joplin offers a wide range of options to sync the notes, but none of them seemed to fit my use case - Except for the Joplin Server. I’m not afaraid in running my own infrastructure. I have some Azure credits available each months, so running a small VM for a Joplin Server is a good way to use them.

VM of choice was a Azure Standard B2s (2 vcpus, 4 GiB memory), running Ubuntu 22.04 LTS. Make sure that you give your VM a public IP and setup a Network Security Group (NSG) to secure what kind of network traffic can reach your VM. I will not going into the details of deploying a Azure VM. Just reach out on Twitter or Mastodon if you have any questions.

Install Docker on Ubuntu 22.04 LTS

To install Docker on my Ubuntu VM, I followed this article on DigitalOcean closely.

You need some prerequisite package to install Docker.

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

Then add the GPG key for the official Docker repository.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

No you can add the repository to the sources.list directory.

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update the packages and then install Docker.

sudo apt install docker-ce docker-compose

Build Joplin Server

To deploy Joplin Server using Docker, it all starts with a YAML file. Create the necessary folders and copy the YAML file into it.

sudo mkdir /opt/joplin-server

Create the joplin-docker-compose.yml under /opt/joplin-server. Please change APP_BASE_URL and MAILER_HOST etc. to reflect your environment.

version: '3'

services:
  db:
    image: postgres:13
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: always
    environment:
      - POSTGRES_PASSWORD=randomString4711
      - POSTGRES_USER=joplin-user
      - POSTGRES_DB=joplindb
  app:
    image: joplin/server:latest
    container_name: joplin-server
    depends_on:
      - db
    ports:
      - "8080:8080"
    restart: always
    environment:
      - APP_PORT=8080
      - APP_BASE_URL=https://notes.blazilla.de/
      - DB_CLIENT=pg
      - POSTGRES_PASSWORD=randomString4711
      - POSTGRES_DATABASE=joplindb
      - POSTGRES_USER=joplin-user
      - POSTGRES_PORT=5432
      - POSTGRES_HOST=db
      - MAILER_ENABLED=1
      - MAILER_HOST=smtp.mailbox.org
      - MAILER_PORT=587
      - MAILER_SECURITY=starttls
      - MAILER_AUTH_USER=user@domain.tld
      - MAILER_AUTH_PASSWORD=LalalaSecurePassword4711
      - MAILER_NOREPLY_NAME=Joplin Server
      - MAILER_NOREPLY_EMAIL=joplin-admin@blazilla.de

Start the Joplin Server.

sudo docker-compose -f joplin-docker-compose.yml up -d

When everything went smooth, you should see the running container using sudo docker ps.

CONTAINER ID   IMAGE                  COMMAND                  CREATED       STATUS       PORTS                                       NAMES
1dd0cdc5e8af   joplin/server:latest   "tini -- yarn start-…"   4 weeks ago   Up 2 weeks   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   joplin-server
1d0be5cf36cc   postgres:13            "docker-entrypoint.s…"   4 weeks ago   Up 2 weeks   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   joplin-server_db_1

Setting up the reverse proxy

The Joplin Server listens in 8080/tcp, which is a bit unhandy. To connect to the Joplin Server using 443/tcp, we need to setup a reverse proxy with NGINX. First step is to install NGINX.

sudo apt install nginx

Then we need to edit the /etc/nginx/sites-available/default. I’m using Let’s Encrypt for TLS certificates. Make sure that you get some using certbot and modify the ssl_certificate and ssl_certificate_key in the default config.

# Default server configuration
#
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;

        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }
}

server {

        root /var/www/html;

        index index.html index.htm index.nginx-debian.html;
        server_name notes.blazilla.de;

        location / {
                proxy_pass http://127.0.0.1:8080$request_uri;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                if ($request_method ~* "(GET|POST)") {
                  add_header "Access-Control-Allow-Origin"  *;
                }

                if ($request_method = OPTIONS ) {
                  add_header "Access-Control-Allow-Origin"  *;
                  add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
                  add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept";
                  return 200;
                }
        }

    listen [::]:443 ssl ipv6only=on;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/notes.blazilla.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/notes.blazilla.de/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

}

server {
    if ($host = notes.blazilla.de) {
        return 301 https://$host$request_uri;
    }

        listen 80 ;
        listen [::]:80 ;
        server_name notes.blazilla.de;
        return 404;
}

Final test

When everything went well, you should be able to connect to the Joplin Server admin interface by using the APP_BASE_URL. Login with the default credentials (admin user with email admin@localhost and password admin). Make sure to change them! Then you can add new users and setup the sync from your Joplin Desktop or smartphone App.