Self-HostingDocker

How to Self-Host Joplin Server on Docker: A Beginner-Friendly Guide to Private Notes Sync

Learn how to self-host Joplin Server on Docker with PostgreSQL, safer defaults, APP_BASE_URL tips, and beginner-friendly private notes sync steps.

AU

Author

David Okonkwo

FTC disclosure: This article contains affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you.

Key Takeaways

  • Joplin Server is the sync backend for Joplin clients, not the note editor itself.
  • For a quick lab test, SQLite can work, but PostgreSQL is the better default for a real always-on setup.
  • The single most important setting is APP_BASE_URL - it must exactly match the address your devices will use.
  • If you only need home-network access, keep the setup simple and use a LAN URL first.
  • If you want outside access, put Joplin behind HTTPS with a reverse proxy instead of exposing random ports and hoping for the best.

If you have ever tried to self-host a note app and felt like every guide assumed you were already halfway through a DevOps certification, you are not imagining it. A lot of Joplin Server tutorials jump straight into Compose files, environment variables, and reverse proxies without slowing down to explain what any of those pieces are doing.

That gets frustrating fast.

Joplin itself is beginner-friendly. Joplin Server setup often is not. The good news is that the actual stack is small once you see the moving parts clearly: one application container, one PostgreSQL database container, and one setting that decides whether your clients sync cleanly or spend the afternoon yelling about invalid origins.

In this guide, we are going to build a clean Joplin Server deployment on Docker, understand why each step matters, and avoid the mistakes that trip up most first-time self-hosters.

If you want a stronger Docker foundation before you start, my guides on Docker volumes vs bind mounts, rootless Docker, and Docker health checks will make the rest of this setup easier to reason about.

What Joplin Server actually does

Before we touch Docker, let us define the parts clearly.

Joplin has clients for desktop and mobile. Those are the apps where you write notes, organize notebooks, and search your content. Joplin Server is the sync engine that keeps those clients in agreement.

Think of it like this:

  • the Joplin app is your notebook
  • Joplin Server is the courier that carries updates between all your notebooks
  • PostgreSQL is the filing room where the server keeps the sync state and metadata organized

Why this matters: if you misunderstand the role of Joplin Server, a lot of deployment choices start to look strange. You are not hosting a web-first notes app like many wiki tools. You are hosting the synchronization backend for clients that already store and edit notes locally.

That is also why Joplin can feel fast and resilient. If your server goes down for a bit, your already-synced notes are still on your devices. You just cannot sync new changes until the server comes back.

When Joplin Server makes sense

Joplin Server is a good fit if you want:

  • private note sync across laptop, desktop, and phone
  • control over where your notes and attachments live
  • a markdown-friendly notes workflow
  • a cleaner dedicated sync backend than generic WebDAV setups

It is especially appealing if privacy is a big part of why you self-host in the first place. That same mindset is why readers who run Vaultwarden or Immich often end up liking Joplin too.

What you need before you start

You do not need enterprise hardware for this. Joplin Server is light enough for a modest home server, mini PC, or NAS that supports Docker.

Minimum practical requirements

  • a Linux server or VM with Docker Engine and Docker Compose v2
  • 2 GB RAM is comfortable for a small personal deployment
  • persistent storage for PostgreSQL data
  • one of these access plans:
  • LAN-only access using a private IP like http://192.168.1.50:22300
  • public access using a domain and HTTPS reverse proxy

Beginner-safe product picks

If you are building a small self-hosting box for apps like this, these are sensible starting points:

Those are not mandatory. They are just the kind of gear that makes a self-hosted notes service feel less like an experiment and more like infrastructure you trust.

Step 1: Create a working directory

Why this matters: keeping one app in one dedicated directory makes backups, upgrades, and troubleshooting much easier. It is the difference between a neat toolbox and a junk drawer.

Run:

sudo mkdir -p /opt/joplin-server
sudo chown "$USER":"$USER" /opt/joplin-server
cd /opt/joplin-server

You can use another path if you prefer, but stay consistent.

Step 2: Create your environment file

Why this matters: an .env file keeps secrets and changeable settings out of the Compose file. That means your database password, domain, and port are easier to update without rewriting the whole stack.

Create .env:

cat > .env <<'EOF'
POSTGRES_USER=joplin
POSTGRES_PASSWORD=replace_with_a_strong_password
POSTGRES_DB=joplin
APP_BASE_URL=http://192.168.1.50:22300
APP_PORT=22300
EOF

chmod 600 .env

Now let us unpack the two settings beginners struggle with most.

POSTGRES_PASSWORD

This is the password Joplin Server uses to talk to PostgreSQL. Make it long and random.

A quick way to generate one:

openssl rand -base64 24

APP_BASE_URL

This tells Joplin Server what address clients will use to reach it.

Examples:

  • LAN only: http://192.168.1.50:22300
  • reverse proxy with HTTPS: https://notes.example.com

This setting must match reality exactly. If your phone syncs to https://notes.example.com, do not set APP_BASE_URL to http://192.168.1.50:22300. That mismatch is one of the biggest causes of broken sync and invalid origin errors.

What could go wrong: if you start with a temporary IP, then later move to a domain, you need to update APP_BASE_URL and restart the container.

Step 3: Write the Docker Compose file

Why this matters: this is where we define the stack. We are using PostgreSQL because it is the safer long-term default for a persistent multi-device sync service. The official Joplin docs allow SQLite for quick tests, but PostgreSQL is the choice I would make once you know the app is staying.

Create docker-compose.yml:

services:
  db:
    image: postgres:16-alpine
    container_name: joplin-db
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: joplin/server:latest
    container_name: joplin-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    env_file: .env
    environment:
      DB_CLIENT: pg
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DATABASE: ${POSTGRES_DB}
      APP_PORT: ${APP_PORT}
      APP_BASE_URL: ${APP_BASE_URL}
    ports:
      - "22300:22300"

Why this version is beginner-friendly

  • PostgreSQL data is persistent under ./data/postgres
  • Joplin waits for the database health check instead of racing it on startup
  • everything important comes from .env
  • the Compose file is short enough to understand in one sitting

One important security note

For a LAN-only lab, exposing 22300:22300 is fine. For an internet-facing deployment, I prefer binding Joplin to localhost and letting a reverse proxy handle public traffic. We will cover that in a later step.

Step 4: Start the stack

Why this matters: the first startup runs migrations and proves your app can reach the database. If something is wrong here, it is much easier to fix now than after you add DNS, HTTPS, and mobile sync.

Run:

docker compose up -d
docker compose ps
docker compose logs -f

What you want to see:

  • both containers running
  • the database health check passing
  • Joplin listening on port 22300 without repeated restart loops

If the logs look noisy, focus on the obvious failures first:

  • authentication errors usually mean your Postgres variables do not match
  • connection refused usually means Joplin started before PostgreSQL was ready, or the DB service is unhealthy
  • origin-related errors usually come later, after the app is reachable but APP_BASE_URL is wrong

Step 5: Verify the app in a browser

Why this matters: this confirms the service is reachable at the same address you expect clients to use.

From another machine on your network, open:

http://192.168.1.50:22300

Replace that with your real APP_BASE_URL.

If the login page loads, the core deployment works.

If it does not:

  1. check the container status again with docker compose ps
  2. confirm the host firewall is not blocking 22300
  3. verify that your server IP in APP_BASE_URL is correct
  4. make sure you are browsing to the same address you put in .env

This step sounds basic, but it saves a lot of time. Beginners often jump straight to configuring mobile sync before confirming the web login page is even reachable.

Step 6: Sign in and change the default admin password

Why this matters: default credentials are a temporary onboarding path, not a real security posture.

Most current guides note the default administrator account as:

  • email: admin@localhost
  • password: admin

Use those only for the first login, then change the password immediately.

What could go wrong: some readers keep using the admin account as their daily sync account. Do not do that. Treat the admin account like the keys to your utility room, not the key you hand to everyone in the house.

Step 7: Create a normal sync user

Why this matters: separating admin duties from normal note syncing is cleaner and safer. If you ever need to manage users, rotate passwords, or audit access, you will be glad you did not tie your everyday note workflow to the root-style account.

Inside the Joplin admin interface:

  1. go to Users
  2. create a normal user account
  3. activate that user if required by your version
  4. use that account on desktop and mobile clients

This is one of those simple habits that makes a homelab feel mature.

Step 8: Configure Joplin clients

Why this matters: the deployment is not useful until your devices sync cleanly.

On desktop or mobile, open Joplin settings and set the sync target to Joplin Server. Then enter:

  • sync URL: your APP_BASE_URL
  • username: your normal user account
  • password: that user's password

Run a manual sync once and watch for errors.

If you plan to use Joplin's end-to-end encryption, enable it in the client. That is separate from self-hosting. Self-hosting controls where the sync backend lives. End-to-end encryption controls whether the note content itself is encrypted between devices.

Step 9: Decide whether you need LAN-only or public HTTPS access

Why this matters: this is where many setups become more complicated than they need to be.

Option A: LAN-only access

If you only need note sync at home or through a private VPN, you can stop here. That is the easier path.

Benefits:

  • fewer moving parts
  • less internet exposure
  • easier troubleshooting

For a lot of beginners, this is the right first version.

Option B: Public access through HTTPS

If you want sync from anywhere, do not just forward port 22300 and call it a day. Put Joplin behind a reverse proxy such as Nginx Proxy Manager, Caddy, Traefik, or plain Nginx, then use HTTPS.

Why this matters: the reverse proxy becomes the secure front door. Joplin stays behind it. That gives you TLS, cleaner DNS, and better control over public exposure.

If you go this route, update your .env file so APP_BASE_URL matches the final HTTPS domain exactly, for example:

APP_BASE_URL=https://notes.example.com

Then restart the app:

docker compose up -d

For reverse-proxy fundamentals, my guide on HTTPS for your homelab is the next thing I would read.

Step 10: Back up the right thing

Why this matters: self-hosting without backups is just volunteering to repeat your setup after the worst possible moment.

For Joplin Server, the most important data lives in PostgreSQL and any persistent app data you keep alongside the stack.

At minimum, back up:

  • /opt/joplin-server/.env
  • /opt/joplin-server/docker-compose.yml
  • /opt/joplin-server/data/postgres/

Even better, take scheduled host-level backups using a tool like Restic. My Restic backup guide pairs nicely with lightweight app stacks like this.

Common mistakes

1. Setting APP_BASE_URL to the wrong address

This is the big one.

If your clients connect through https://notes.example.com, then that is what APP_BASE_URL should be. Not the private IP. Not a hostname you only use internally. Not a guess.

2. Exposing PostgreSQL when you do not need to

Some tutorials map port 5432 to the host. For most beginners, that is unnecessary. Let PostgreSQL stay on the internal Docker network unless you have a real reason to reach it directly.

3. Skipping persistence

If your database data is not stored on a persistent volume or bind mount, a container recreation can wipe your sync backend. That is not a Joplin problem. That is a Docker storage problem.

4. Using the admin account for daily sync

Create a normal user. Future you will appreciate the separation.

5. Making the internet-facing version your first version

If you are brand new to reverse proxies, TLS, and DNS, start with LAN-only access. It is much easier to debug one layer at a time.

FAQ

Can I run Joplin Server without PostgreSQL?

For testing, yes. The official Joplin Server documentation allows a simple SQLite-backed run. For a real always-on deployment, PostgreSQL is the better default because it is more durable and better aligned with how most self-hosters manage multi-device services.

What should APP_BASE_URL be if I only use Joplin at home?

Use the exact LAN address your devices will open, such as http://192.168.1.50:22300. If you later switch to a domain and HTTPS, update it.

Do I need a reverse proxy?

Not for LAN-only access. Yes, or at least something equivalent, if you want a cleaner and safer public HTTPS setup.

Is Joplin Server better than syncing through Dropbox or WebDAV?

If your goal is privacy and control, yes, it is often the better fit. It is purpose-built for Joplin sync rather than being a generic storage workaround.

How much hardware does Joplin Server need?

Very little for personal use. A small VM, a low-power mini PC, or a NAS with Docker support is usually enough.

What to learn next

Once Joplin Server is working, the next skills worth building are not glamorous, but they matter:

That progression matters because self-hosting is not just about making an app appear in a browser. It is about building a setup you can still trust three months from now when you have forgotten the exact command you used on day one.

If you want the official references while you work, keep the Joplin Server README, the Docker Compose file reference, and the PostgreSQL Docker image docs nearby.

Joplin Server is one of the better beginner self-hosting wins because the payoff is immediate. You install it, point your devices at it, and suddenly your notes sync on your terms. That is a small change on paper, but it is the kind of small change that makes a homelab feel personal instead of theoretical.