Guide for Joplin-Server on Raspberry Pi

Hi,

this is an extensive guide on how to host your own Joplin server on a Raspberry Pi. Until now I had a setup like this running on a Pi 4 and a Pi 5.

If you spot any mistakes, please reply to me!


The guide was overhauled on 10.12.2023.


1 Dynamic DNS

If you don't get a static IP address from your ISP, you will need a Dynamic DNS service. This sets a hostname which will be redirected to your IP. There are a lot of providers for this, currently I use No-IP because it is free and has always worked out for me. So create an account here and set a hostname with your (external) IP address, which you can read here. Make sure you don't have a VPN active, because then the IP address will differ.

2 Port forwarding

For all this fun to work, you need to give your Pi a static IP in your router and set up port forwarding to it for port 80 and 443. There are enough instructions on how to do this all over the internet.

3 Raspberry Pi OS 64-bit

Raspberry Pi OS (formerly known as Raspbian) is 32-bit by default. But for the server you need a 64-bit system. Starting with the Raspberry Pi 3 (and some models of the 2nd generation) a 64-bit chip is installed.
To install the system image, you now need a SD card (I recommend at least 8 GB) and the Raspberry Pi Imager. After opening it up, select your Raspberry Pi and under "Operating System" go to "Raspberry Pi OS (other)" and choose "Raspberry Pi OS Lite (64-bit)". Then select your SD card, click on "Next" and "Edit settings":

General:

  • Activate "Set username and password", choose a username ("pi" is often used) and a strong password.
  • If needed, setup your WiFi credentials. Note: LAN is recommended as WiFi is often unstable.
  • Set your locale settings.

Services:

  • If you plan using your Pi headless (without keyboard and monitor), enable SSH and paste you public-key(s). If you don't know what a public key is, choose "password authentication".

Click "Save" and "Yes". After the image has been written, remove the SD card, insert it into your Pi and connect a keyboard and monitor to it or connect via SSH (tutorials for this can be found on the web) and login using your specified credentials.

4 Docker and docker-compose

Type in the following commands (each line separately):

sudo apt update; sudo apt upgrade
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

5 Joplin server

Now create a docker-compose.yml file

nano docker-compose.yml

and paste the following:

version: '3'
services:
    db:
        restart: unless-stopped
        image: postgres:latest
        ports:
            - "5432:5432"
        volumes:
            - /data/joplin-data:/var/lib/postgresql/data
        environment:
            - POSTGRES_PASSWORD=CHANGEME
            - POSTGRES_USER=joplin
            - POSTGRES_DB=joplin
    app:
        environment:
            - APP_BASE_URL=https://YOUR-DOMAIN.COM/joplin
            - APP_PORT=22300
            - POSTGRES_PASSWORD=CHANGEME
            - POSTGRES_DATABASE=joplin
            - POSTGRES_USER=joplin
            - POSTGRES_PORT=5432
            - POSTGRES_HOST=db
            - DB_CLIENT=pg
        restart: unless-stopped
        image: etechonomy/joplin-server:latest
        ports:
            - "22300:22300"
        depends_on:
            - db

Replace YOUR-DOMAIN.COM with your hostname from No-IP and the two CHANGEME's with the same strong password.

Note: This guide configures the server to serve Joplin out of a subdirectory (https://YOUR-DOMAIN.COM/joplin). If you'd like to have it served on your root (directly via https://YOUR-DOMAIN.COM), simply omit the /joplin in the APP_BASE_URL.

Now enter the following command to start Docker compose. This process will take a good while.

sudo docker compose up -d

6 Webserver / Proxy

You have several options for proxying encrypted network traffic from outside to your Joplin server. For beginners I recommend Apache.

Apache (recommended)

6.1 Apache web server

To install Apache type the following command:

sudo apt install apache2

Further configuration is not necessary until later.

6.2 Certbot / Let's Encrypt:

To install Certbot and thus create a Let's Encrypt SSL certificate, enter the following commands:

sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --apache

After the last command you have to register with your email address (for important notifications about your certificates) and accept the terms. You can decide if you want to share your e-mail address with EFF. Then enter your previously chosen hostname from No-IP.
Afterwards check the success by typing the hostname of No-IP into your browser. You should now be taken to the default Apache page via HTTPS.

6.3 Apache again

Now the further configuration. Enable the proxy modules:

sudo a2enmod proxy
sudo a2enmod proxy_http

And open the config for ssl:

sudo nano /etc/apache2/sites-enabled/000-default-le-ssl.conf

Add the following between <VirtualHost *:443> and </VirtualHost>:

ProxyPreserveHost On
ProxyPass "/joplin" http://localhost:22300
ProxyPassReverse "/joplin" http://localhost:22300

Note: If you'd like to have Joplin served from your root domain, replace the two /joplin's by /.

Quit by pressing Ctrl + O, Enter, Ctrl + X and restart Apache:

sudo apachectl -k graceful

The Joplin interface should now be available under HTTPS://YOUR-DOMAIN.COM/joplin.

Nginx

6.1 Nginx web server

To install Nginx type the following command:

sudo apt install nginx

Further configuration is not necessary until later.

6.2 Certbot / Let's Encrypt:

To install Certbot and thus create a Let's Encrypt SSL certificate, enter the following commands:

sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx

After the last command you have to register with your email address (for important notifications about your certificates) and accept the terms. You can decide if you want to share your e-mail address with EFF. Then enter your previously chosen hostname from No-IP.
Afterwards check the success by typing the hostname of No-IP into your browser. You should now be taken to the default Nginx page via HTTPS.

6.3 Nginx again

Now the further configuration. Open the config:

sudo nano /etc/nginx/sites-enabled/default

Add the following into the second last server block (the one where ssl is handled):

proxy_set_header X-Forwarded-Host $host;
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;

location /joplin/ {
    proxy_redirect off;
    rewrite ^/joplin/(.*)$ /$1 break;
    proxy_pass http://127.0.0.1:22300;
}

(copied from here)
Note: If you'd like to have Joplin served from your root domain, replace the /joplin/ with a / and remove the two lines under it.

Quit by pressing Ctrl + O, Enter, Ctrl + X and restart Nginx:

sudo nginx -s reload

The Joplin interface should now be available under HTTPS://YOUR-DOMAIN.COM/joplin.

Nginx Proxy Manager (only root domain, no subdirectory)

To use Nginx Proxy Manager, you first have to add another container to the docker-compose.yml file:

sudo docker compose down
nano docker-compose.yml

Add the following (be sure the indents match the rest of the file):

    npm:
        image: "jc21/nginx-proxy-manager:latest"
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
            - "81:81"
        volumes:
            - /data/nginx-proxy-manager:/data
            - /data/letsencrypt:/etc/letsencrypt

And fire it back up:

sudo docker compose up -d

Now browse to the local IP of your Pi, followed by :81 (for example 192.168.0.100:81) and type in admin@example.com as e-mail and changeme as password. Then setup a new user with a strong password.

Click on "Hosts", "Proxy Hosts", "Add Proxy Host", type in your domain name from No-IP. Leave the protocol on http and type the local IP of your Pi into "Forward Hostname / IP". Port will be 22300. You can optionally enable "Cache Assets" and "Block Common Exploits". In the SSL tab, select "Request a new SSL Certificate" and activate "Force SSL" as well as "HTTP/2 Support" and agree to the terms of service. The e-mail adress you type in here is for important notifications about your certificates.Then click "Save".

The Joplin interface should now be available under HTTPS://YOUR-DOMAIN.COM.

7 Login

To login, browse to YOUR-DOMAIN.COM/joplin. The default e-mail is admin@localhost with admin as password. Please change those credentials immediately and add a non-admin user under the Users tab for syncing your notes. Keep in mind that the server is accessible to anyone on the internet and therefore strong passwords are highly recommended.

8 Activate Dynamic-DNS

Note: some routers have in-built capabilities to update your DynDNS IP. You can use those as well.

To keep the IP address at No-IP up to date, you have to install and set up the update client of No-IP per these instructions. In order to keep the service active after a reboot, enter the following command:

sudo crontab -e

and add the following line to the end of the document:

@reboot sudo /usr/local/bin/noip2

9 Unattended security updates (Optional)

To enable automatic security updates install the unattended-upgrades package and configure it:

sudo apt install unattended-upgrades apt-listchanges
echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true | sudo debconf-set-selections
sudo dpkg-reconfigure -f noninteractive unattended-upgrades

10 Joplin server updates

Be sure to always have a backup of your data before changing anything!

If you want to update the server, stop the server, pull the new image and bring it back up again:

sudo docker compose down; sudo docker compose pull; sudo docker compose up

Check if everything runs fine. If so, stop the server again with Ctrl + C and start it in detached mode to let it run in the background:

sudo docker compose up -d

Note: If you don't want to do updates to your server regularly, check out the watchtower container. I didn't include this into my guide as you should be very careful when automatically updating a database.


Thanks for any hints and improvements as well as the arm-compiled images:


Kind regards
MrKanister :smiley:

21 Likes

One small point: you can install the 64bit image entirely headless if you want to: after the card has been hurned, take it out of the PC and reinsert it, which will load up the boot partition in file explorer/manager. Then i that partition make a file called "ssh" -- nothing more, and no extension. It can be empty or, for all I know, it can contain the whole of wikipedia. What matters is that it is on the boot partition and just called "ssh". If this is detected during the boot process on the pi, the ssh connection will start up and you can connect without adding a screen or keyboard to the pi.

1 Like

Just for info, Raspberry Pi also have a beta 64-bit "Lite" image.

1 Like

WHen I try this, I get endless errors, of the form

app_1  | 2021-03-13 12:43:14: App: PUT /api/files/root:/Apps/Joplin/efd32ec21c474f059ebc31a4ced73a33.md:/content
app_1  | 2021-03-13 12:43:14: App: GET /api/files/root:/Apps/Joplin/e2e02f1c01a24ddeb23d07bad44f9aab.md:
app_1  | 2021-03-13 12:43:14: [error] App: 404: GET /api/files/root:/Apps/Joplin/e2e02f1c01a24ddeb23d07bad44f9aab.md: : file not found: root:/Apps/Joplin/e2e02f1c01a24ddeb23d07bad44f9aab.md:

(Obviously, the exact filename varies)
All I have changed from the defaults is the URL of my joplin installation, and pinging the server to see if it is live gets, I think, the right response, ie

PS C:\Users\Andrew> curl http://joplin.mydomain:22300/api/ping


StatusCode        : 200
StatusDescription : OK
Content           : {"status":"ok","message":"Joplin Server is running"}
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Content-Length: 52
                    Content-Type: application/json; charset=utf-8
                    Date: Fri, 12 Mar 2021 17:57:50 GMT

                    {"status":"ok","message":"Joplin Server is running"}
Forms             : {}
Headers           : {[Connection, keep-alive], [Content-Length, 52], [Content-Type, application/json; charset=utf-8], [Date, Fri, 12 Mar
                    2021 17:57:50 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 52

The only strange thing is that in the response to my docker-compose command, I see the line "Running in Docker: false" . See below:

╭─pi@Quinn ~/docker
╰─➤  docker-compose --file ./simple-joplin-server.yml up
Starting docker_db_1  ... done
Starting docker_app_1 ... done
Attaching to docker_app_1, docker_db_1
db_1   |
db_1   | PostgreSQL Database directory appears to contain a database; Skipping initialization
db_1   |
db_1   | 2021-03-13 12:49:46.979 UTC [1] LOG:  starting PostgreSQL 13.1 (Debian 13.1-1.pgdg100+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
db_1   | 2021-03-13 12:49:46.980 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db_1   | 2021-03-13 12:49:46.980 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db_1   | 2021-03-13 12:49:46.991 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1   | 2021-03-13 12:49:47.004 UTC [25] LOG:  database system was shut down at 2021-03-13 12:45:57 UTC
db_1   | 2021-03-13 12:49:47.020 UTC [1] LOG:  database system is ready to accept connections
app_1  |
app_1  | > @joplin/server@1.7.2 start /home/joplin/packages/server
app_1  | > node dist/app.js
app_1  |
app_1  | 2021-03-13 12:49:51: App: Starting server (prod) on port 22300 and PID 18...
app_1  | 2021-03-13 12:49:51: App: Running in Docker: false
app_1  | 2021-03-13 12:49:51: App: Public base URL: http://joplin.mydomain:22300
app_1  | 2021-03-13 12:49:51: App: Log dir: /home/joplin/packages/server/logs
app_1  | 2021-03-13 12:49:51: App: DB Config: {
app_1  |   client: 'pg',
app_1  |   name: 'joplin',
app_1  |   user: 'joplin',
app_1  |   password: '********',
app_1  |   port: 5432,
app_1  |   host: 'db'
app_1  | }
app_1  | 2021-03-13 12:49:51: App: Trying to connect to database...
app_1  | 2021-03-13 12:49:51: App: Connection check: {
app_1  |   latestMigration: { name: '20190913171451_create.js' },
app_1  |   isCreated: true,
app_1  |   error: null
app_1  | }
app_1  | 2021-03-13 12:49:51: App: Migrating database...
app_1  | 2021-03-13 12:49:51: App: Call this for testing: `curl http://joplin.mydomain:22300/api/ping`
1 Like

Hi! Which URL did you put in your Joplin-Client Configuration?

EDIT:
I think, you might have done the same mistake I did at the beginning...take a look at Failing to install HTTPS on Joplin Server and let me know, if that fixes your problems :smiley:

I updated this guide and added the two hints, as well as section 11, describing the update process. - 27.06.2021

Kind regards

4 Likes

Hi All,

Did everything what tutor show. Can login using given HTTPS. Can change default email/password

But when i use the iOS app to sync i see this error:


pp_1  | 2021-07-21 14:47:27: [error] App: 400: PUT /api/batch_items : Not allowed: PUT
app_1  | 2021-07-21 14:47:27: App: PUT /api/batch_items (2ms)
app_1  | 2021-07-21 14:47:37: [error] App: 400: PUT /api/batch_items : Not allowed: PUT
app_1  | 2021-07-21 14:47:37: App: PUT /api/batch_items (1ms)

I created the docker with this line

/foo/bar/joplin-data:/var/lib/postgresql/data

Do i change the /foo/bar/ part ?

@poudenes welcome to the forum.

Change that to put the Joplin data wherever you want it to be on the system running Docker. Now you've run docker-compose you'll probably see your Pi now has a /foo/bar/joplin-data folder on it.

With regards to the put errors, how up to date is your iOS app?

Also latest is not actually being fully used for the server image yet as it is still beta software. The tag is therefore referencing an old beta version. If you want to try the latest server beta version you will need to use florider89/joplin-server:2.2.7-beta

Thanks for the bast reply. My iOS data is very updates. (synced before with dropbox) On my iMac i crreated a export of all data to have a backup. Now will do a clean install with your last part of the beta.

Then try again to sync iOS app and if that is working then also iMac App to sync also.
On the iMac app have a backup plugin to backup every time a backup... So when somethings go wrong its ok

p.s. The apache part i skipped. Have some things running behind Nginx Proxy Manager. Also Joplin now with http. Later on create a SSL and swith to https

Sorry for not being clear.

By "how up to date is your iOS app" I meant the version of the iOS app itself. The AppStore shows the latest available version is 12.1.2.

haha 12.1.2 :slight_smile:

WIth the updates in the compose file the sync is working correct !!!
Thanks

Another question: When i backup the path location that i put in the compose file and system will crash. After reinstall the things i can restore the data into the folder again and start the docker again?

Are you saying that if you try to back-up /foo/bar/joplin-data your system crashes?

No want i want say: is there a way to backup the postgres database if system have a crash.
Found this:

Backup postgres:

docker exec -t your-db-container pg_dumpall -c -U username > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql

Restore postrgres

cat your_dump.sql | docker exec -i your-db-container psql -U username

The backup part worked :slight_smile: so can build a cronjob for daily backups

I cannot help you there as you are doing something I have never tried.

I backup using the Joplin Desktop client and @JackGruber's excellent "Simple Backup" Plugin.

Use that also :slight_smile:

Hi All,

I want to run joplin server locally on my raspberry pi using docker. I want to access it from another machine on the same network using its IP address. What do I enter for the APP_BASE_URL parameter in docker-compose.yml?

the ip address of the raspberry pi http://192.168.0.xx/joplin usually. I tried putting http://localhost/joplin but if I remember correctly i kept getting an error “invalid login path” something like that

This is exactly what I'm facing. I'm satisfied with using the ip address itself but <ip_address>/joplin does not work for me. I get "invalid login path" as a message.

I also tried setting APP_BASE_URL=192.168.0.x/joplin
When I visited the above link on another machine, I got an error "invalid login path" and I was given a link to go to the login page. On clicking on that, the url in the address bar changes to:
192.168.0.x/192.168.0.x/joplin {Ip address appears 2 times}

I dont want to set up dyndns for this. Any info will be greatly appreciated.

Hmm.. sorry I remember it had worked but it was over a month or so ago I have my own domain so I switched to that after testing it out. sorry I couldn’t help