Guide for Joplin-Server on Raspberry Pi


after I managed to host my own Joplin server on a Raspberry Pi, I decided to write a guide that might help other people. If you spot any mistakes, please reply to me!

The guide was lastly updated on 02.03.2022.

1. Dynamic DNS

If you didn'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. 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 be different.

2. Port forwarding

For all this fun to work, you need to give the RasPi a static IP in your router and set up port forwarding to it for port 80 and 443. There are enough instructions for this elsewhere.

3. Raspberry Pi OS 64bit

Raspberry Pi OS (formerly Raspbian) is 32bit by default. But for the server you need a 64bit system. Starting with the Raspi 3 (and some models of the 2nd generation) a 64bit chip is installed. To use the 64bit system you now need a SD card on which you write the beta version of Raspberry Pi OS in the 64bit variant: Link (thanks to @dpoulton for the hint!). For writing you can either use the Raspberry Pi Imager, or e.g. the Win32 Disk Imager. There are enough instructions elsewhere.

After the image has been written, remove the SD card and reinsert it once again into the PC. The boot partition should now appear in the file manager. Create an empty file there with the name "ssh" (thanks to @seatrout for the hint!) and make sure that it is created without a file extension. Then put the SD card in the Raspberry Pi, boot it up and connect via SSH. Keep in mind to change the default password: Link

4. Docker and docker-compose

Type in a terminal window, or via SSH the following commands (each line separately):

sudo apt update && sudo apt upgrade
curl -sSL | sh
sudo usermod -aG docker ${USER}

Then reboot your Raspi:

sudo reboot now

Docker is installed, now follow Docker compose:

sudo apt-get install libffi-dev libssl-dev
sudo apt install python3-dev
sudo apt-get install -y python3 python3-pip
sudo pip3 install docker-compose

This will install docker-compose.

5. Apache web server

In a terminal window, or via SSH, type the following command:

sudo apt install apache2

Further configuration is not necessary until later.

6. Certbot:

To create Certbot and thus an 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 login with your email address and accept the terms. Then enter your previously given hostname from No-IP.
Afterwards check this by typing the hostname of No-IP into your browser. You should now be taken to the default Apache page via HTTPS. If you want, you can disable port forwarding for port 80 now.

7. 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 here between the lines <VirtualHost *:443> and </VirtualHost> the following lines:

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

And restart Apache:

sudo systemctl restart apache2

8. Joplin server

Now create a docker-compose.yml file....

nano docker-compose.yml

...and replace DOMAIN.COM with your hostname from No-IP. Big thanks to @etho201 and @florider at this point!

In the line image: etechonomy/joplin-server:latest you can choose, which version of the server you will be running. Choose the best one for you (take a look at the changelogs and the forum) and then type the corresponding tag behind the colon (in this example it is the latest - tag,). The tags can be viewed here: etechonomy/joplin-server - tags.

version: '3'
        restart: unless-stopped
        image: postgres:13.1
            - "5432:5432"
            - /foo/bar/joplin-data:/var/lib/postgresql/data
            - POSTGRES_PASSWORD=joplin
            - POSTGRES_USER=joplin
            - POSTGRES_DB=joplin
            - APP_BASE_URL=https://DOMAIN.COM/joplin
            - APP_PORT=22300
            - POSTGRES_PASSWORD=joplin
            - POSTGRES_DATABASE=joplin
            - POSTGRES_USER=joplin 
            - POSTGRES_PORT=5432 
            - POSTGRES_HOST=db
            - DB_CLIENT=pg
        restart: unless-stopped
        image: etechonomy/joplin-server:latest
            - "22300:22300"
            - db

Now enter the following command to start Docker compose. This process will take a good while the first time you call it and after each update.:

docker-compose up

Check if you get to the Joplin server login page with your hostname from No-IP and a final /joplin. If so, stop the server again with Ctrl + C and start it in detached mode to let it run in the background:

docker-compose up -d

9. Login

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

10. Activate Dynamic-DNS

To keep the IP address at No-IP up to date, you still have to install and set up the update client of No-IP with this instruction. In order to make the service active even 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

11. Updates

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

If you want to update the server and have not selected a "variable" tag (master or latest), first stop the server:

docker-compose down

And edit the docker-compose.yml file according to your needs (described at point 8):

nano docker-compose.yml

After the change, start the server with

docker-compose up

and 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:

docker-compose up -d

Kind regards
Mister Kanister :smiley:


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/
app_1  | 2021-03-13 12:43:14: App: GET /api/files/root:/Apps/Joplin/
app_1  | 2021-03-13 12:43:14: [error] App: 404: GET /api/files/root:/Apps/Joplin/ : file not found: root:/Apps/Joplin/

(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 "", 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?

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


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


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 !!!

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