If you've ever lost a note because a cloud note app changed its pricing, got acquired, or just quietly killed the free tier, you already understand the appeal of Joplin. It's a free, open-source note-taking app with end-to-end encryption, and it works on Windows, macOS, Linux, Android, and iOS. The catch is that syncing your notes between devices needs a backend somewhere, and Joplin's own hosted option — Joplin Cloud — costs money and puts your (encrypted, but still) data on someone else's server.
Running your own Joplin Server fixes both problems. It's free once you have somewhere to run it, and if you already have a Proxmox VE box humming away in a closet or a rack, that "somewhere" is sitting right there. This tutorial walks you through spinning up a dedicated LXC container for Joplin Server, getting your first login working, and pointing your Joplin apps at it.
What You Will Learn
By the end of this guide you'll have a working Joplin Server running in its own lightweight Proxmox VE container, and you'll know how to:
- Create a purpose-built LXC container for Joplin Server using the community-scripts helper script
- Log in to the web admin panel and change the default credentials
- Connect the Joplin desktop and mobile apps to your new server
- Read the container's logs when something isn't syncing right
- Apply a few settings that will save you a support headache six months from now
What Is Joplin Server?
Joplin Server is the self-hosted sync backend for the Joplin note-taking app. Think of it as the piece that sits in the middle: your laptop's Joplin app, your phone's Joplin app, and your desktop at work all talk to this one server, and it keeps every copy in sync. Without a server — self-hosted or Joplin Cloud — Joplin still works fine as a local note app, but your notes stay stuck on one device.
Under the hood it's a Node.js application that stores your note data (still end-to-end encrypted if you turn that on in the client) in a database, normally PostgreSQL for anything beyond a quick test. It's not a note-taking app itself — you won't type notes into Joplin Server's web interface. That part still happens in the Joplin apps you already use. The server's whole job is sync and account management.
A couple of terms that'll come up in this guide, explained once so you're not googling mid-tutorial:
- LXC container — a lightweight, OS-level virtualization method built into Proxmox VE. Unlike a full virtual machine, an LXC container shares the host's Linux kernel, so it starts in a couple of seconds and uses a fraction of the RAM a VM would need for the same job. It's the right tool here because Joplin Server doesn't need its own kernel, its own boot process, or GPU passthrough — it just needs Node.js, a database, and a bit of disk space.
- Docker Compose — a tool for defining and running multi-container applications from a single YAML file. Joplin Server's official deployment method uses it to start the app and its PostgreSQL database together with one command instead of wiring them up by hand.
Why Would You Use It?
The honest answer: privacy and cost. Joplin Cloud is reasonably priced for what it is, but paying a recurring fee to sync plain text notes rankles some people, myself included. If you're already running Proxmox VE for other self-hosted apps, adding Joplin Server costs you almost nothing extra — the community-scripts default is 2 CPU cores, 6 GB of RAM, and 20 GB of disk, and in practice it idles at a small fraction of that.
There's also the control angle. You decide where backups live, you decide the retention policy, and if you ever want to inspect exactly what's stored, you can SSH in and look. For a lot of homelab users, that alone is worth the twenty minutes this setup takes.
I'll add one honest caveat: if you only ever use Joplin on a single laptop and never need to sync to a phone or second machine, you don't need any of this. Local storage works fine for that case, and standing up a server would just be something else to patch and maintain.
Prerequisites
Before you start, make sure you have:
- A working Proxmox VE host — this guide was written against Proxmox VE 9.2, running on Debian 13.5 "Trixie" with LXC 7.0, but the steps are the same on any current 8.x or 9.x install
- At least 20 GB of free space on the storage you plan to use for the container, and around 6 GB of RAM you're comfortable dedicating (you can trim this down later — more on that in Best Practices)
- Root or sudo access to the Proxmox VE shell, either through the web UI's >_ Shell button on your node, or over SSH
- A downloaded Linux container template available in your storage (Proxmox will offer to pull one automatically if you don't have one yet)
- Internet access from the Proxmox host, since the installer script pulls packages and container images from the internet during setup
You don't need to know Docker, PostgreSQL, or Node.js for this tutorial. The install script handles all three for you. It helps to have used the Joplin app once before, just so the login screen makes sense later.
Step-by-Step Tutorial
Step 1: Open the Proxmox VE Shell
Log in to the Proxmox VE web interface, click your node's name in the left-hand tree, and click >_ Shell near the top. This drops you into a root shell on the Proxmox host itself — not inside any VM or container.
Step 2: Run the Joplin Server install script
Paste this into the shell and press Enter:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/joplin-server.sh)"
This is a helper script from the community-scripts project, an open-source collection of installers that automate the boring parts of standing up self-hosted apps on Proxmox VE — creating the container, installing dependencies, and configuring the app so it starts on boot. It's the same family of scripts used for a lot of the other app-install guides on this site. You're trusting a third-party script here, which is a fair thing to be cautious about — if you'd rather not, the script itself is plain bash and worth a skim before you run it.
Step 3: Choose Default or Advanced settings
The script asks whether you want Default Settings or Advanced Settings. Default gives you a Debian 13 container with 2 cores, 6144 MB of RAM, and 20 GB of disk, on the next free container ID. That's a sensible starting point for a household's worth of Joplin syncing, so unless you specifically need a different VMID, hostname, or storage target, Default is the one to pick.
If you do choose Advanced, you'll be walked through container ID, hostname, disk size, CPU cores, RAM, and which storage pool to use — the same fields you'd set manually with pct create, just prompted one at a time.
Step 4: Let the script finish
The script now creates the container, boots it, updates package lists, installs Docker and Docker Compose inside it, pulls the Joplin Server and PostgreSQL images, and writes a configuration file. This step is the slow part — expect somewhere around three to six minutes depending on your internet connection and disk speed. You'll see scrolling output the whole time; it's normal for there to be long pauses during the Docker image pulls.
When it's done, the script prints a summary with the container's IP address and the port Joplin Server is listening on (22300 by default). Write that down.
Step 5: Open the Joplin Server web interface
From a browser on the same network, go to http://<container-ip>:22300. You should land on the Joplin Server login page. Log in with the default credentials:
- Email:
admin@localhost - Password:
admin
Change that password immediately — go to the admin panel's user settings and set something real. Leaving the default credentials in place is the single most common way people get their Joplin Server compromised, and since this server is reachable from any device on your network, it's not a hypothetical risk.
Step 6: Connect the Joplin app to your server
Open Joplin on your desktop or phone, go to Options / Configuration > Synchronisation, and set the target to Joplin Server. Enter your container's address (http://<container-ip>:22300) along with the email and password you just set. Hit Check synchronisation configuration — if it comes back green, you're syncing. Repeat this on every device you want in sync.
Commands Explained
A few commands are worth understanding rather than just copy-pasting, in case you need to troubleshoot later.
| Command | What it does |
|---|---|
pct list | Run on the Proxmox host, lists every LXC container with its ID, status, and name — useful for confirming the script created what you expected. |
pct enter <VMID> | Drops you into a root shell inside the container, as if you'd SSH'd in. Replace <VMID> with the container ID shown by pct list. |
docker compose ps | Run inside the container (after cd /opt/joplin-server), shows whether the Joplin Server and PostgreSQL containers are actually running. |
docker compose logs -f | Streams live logs from the Joplin Server container. This is the first place to look if login or sync fails after setup. |
docker compose restart | Restarts the Joplin Server and database containers without rebuilding them — the equivalent of turning it off and on again, and it fixes more than you'd expect. |
Common Errors
"This site can't be reached" when opening the web interface. Usually means either the container isn't finished booting yet (give it another minute after the script exits), or you've got the wrong IP. Run pct list on the host, then pct enter <VMID> followed by ip a inside the container to confirm the actual address.
Login page loads but credentials are rejected. Double-check you're using admin@localhost, not just admin, for the email field — it trips people up because most login forms don't expect a fake-looking email address.
Sync check fails from the Joplin app with a connection error. Confirm the port. The default is 22300, and it's easy to typo as 2300 or forget entirely when typing the sync URL into the app.
Sync check fails with an "Invalid origin" or similar mismatch error after you've put the server behind a reverse proxy. This means the APP_BASE_URL value in the container's /opt/joplin-server/.env file doesn't match the address you're actually browsing to. Edit that file so APP_BASE_URL matches your real domain, then run docker compose down && docker compose up -d from /opt/joplin-server to apply it.
Troubleshooting
If something's wrong and the errors above don't cover it, work through this order:
- Check the container is actually running with
pct status <VMID>on the Proxmox host. If it's stopped, start it withpct start <VMID>. - Enter the container with
pct enter <VMID>and check the Docker containers withcd /opt/joplin-server && docker compose ps. Both the app and the database container should show as running (Up), not restarting or exited. - If either container is restarting in a loop, read the logs with
docker compose logs— nine times out of ten it's a database connection issue, and the error message will usually say so plainly. - Check disk space inside the container with
df -h. A full disk will silently break the database and give you confusing errors everywhere else. - If nothing else works,
docker compose down && docker compose up -dfrom/opt/joplin-servergives you a clean restart without touching your data, since the database volume persists separately from the containers.
Best Practices
Change the default admin password before you do anything else — this is worth repeating because it's the step people skip when they're excited to start syncing.
Don't leave port 22300 exposed directly to the internet if you want access away from home. Put it behind a reverse proxy with a real TLS certificate instead — Nginx Proxy Manager or Caddy both work well for this, and either lets you reach your notes over HTTPS on a normal domain instead of a raw IP and port.
Back up the container regularly. A quick way is a scheduled vzdump backup job targeting this container's VMID, either to local storage or, better, to a Proxmox Backup Server instance if you have one. Your notes are small — the whole point of Joplin is plain text — so backups are cheap and fast.
The default 6 GB of RAM is generous for a personal or small-household deployment. If you're tight on host memory, you can lower it after the fact with pct set <VMID> -memory 2048 from the Proxmox shell, and Joplin Server for a handful of users will barely notice.
Enable end-to-end encryption inside the Joplin app itself (under Configuration > Encryption), not just on the server. Joplin Server stores your data, but it doesn't know your encryption passphrase unless you tell it — that separation is the whole point.
Frequently Asked Questions
Is Joplin Server actually free?
Yes. It's open source and free to self-host; you're only paying for the electricity and hardware you already own.
Can I use SQLite instead of PostgreSQL?
Technically yes for quick local testing, but PostgreSQL is what the official deployment and this script use, and it's the only option recommended for anything you actually rely on day to day.
Does this work with the Joplin mobile apps?
Yes, both the Android and iOS apps support Joplin Server as a sync target the same way the desktop app does.
Is my data end-to-end encrypted on the server?
Only if you turn on encryption inside the Joplin app before you start syncing. Without it, the server can read your notes; with it, the server only ever sees encrypted blobs.
Can I access my notes from outside my home network?
Yes, but you'll want a reverse proxy with HTTPS, or a VPN like WireGuard or Tailscale, rather than exposing port 22300 directly.
What happens if I lose the container?
You lose your synced note history unless you've backed it up, which is exactly why the vzdump step under Best Practices isn't optional in my book.
Conclusion
Twenty minutes and a spare container ID gets you a private, ad-free, subscription-free place for your notes to live. It's a small project as Proxmox homelab tasks go, but it's one of those setups that quietly pays for itself the first time a cloud service you don't control changes its terms on you. Change that default password, put a real certificate in front of it if you plan to use it away from home, and back it up like you would anything else you'd hate to lose.