A dynamic web server that serves published Joplin notes as a public website. It reads directly from Joplin's PostgreSQL database, renders markdown server-side (with math and syntax highlighting), and serves everything through an Astro SSR frontend.
New notes appear automatically within the cache TTL — no rebuild required.
For this to properly work, you must be self-hosting Joplin server using the PostgreSQL database with docker-compose; see the offical documentation for instructions on getting this setup.
Once setup, this application works like follows:
Joplin desktop → Joplin Server (Docker) → PostgreSQL
↓
joplin-sidecar
(Astro SSR + Node)
↓
nginx → Cloudflare Tunnel → internet
- Only notes marked as shared in Joplin desktop app are served.
- The note list is cached for
CACHE_TTL_MSms (default 30 s); the UI rebuilds only when.astroor CSS files change.
- Node.js
^18.17.1 || ^20.3.0 || >=21.0.0 - Joplin Server running via Docker with PostgreSQL
- nginx
- A Cloudflare Tunnel pointed at
http://127.0.0.1:80(or other proxy to expose website to the internet)
Set these in your systemd service or a .env file:
| Variable | Default | Description |
|---|---|---|
PG_HOST |
127.0.0.1 |
PostgreSQL host |
PG_PORT |
5432 |
PostgreSQL port |
PG_USER / POSTGRES_USER |
— | PostgreSQL username |
PG_PASSWORD / POSTGRES_PASSWORD |
required | PostgreSQL password |
PG_DATABASE / POSTGRES_DATABASE |
joplin |
Database name |
LISTEN_PORT |
3456 |
Port the Node server listens on |
CACHE_TTL_MS |
30000 |
Note list cache TTL in milliseconds |
SITE_TITLE |
Notes |
Title shown in the sidebar and index page |
git clone <repo-url> joplin-sidecar
cd joplin-sidecar
npm installnpm run buildThis compiles the Astro frontend to dist/. Re-run whenever you change .astro, CSS, or config
files. Content changes (new/updated notes in Joplin) do not require a rebuild.
# With env vars inline:
PG_PASSWORD=secret SITE_TITLE="Notes" node server.js
# Or export them first, then:
node server.jsCopy and edit the included service file:
sudo cp joplin-sidecar.service /etc/systemd/system/
# Edit credentials and paths as needed
sudo systemctl daemon-reload
sudo systemctl enable --now joplin-sidecarThe service runs npm run build automatically before starting the server on every boot/restart.
To apply UI changes without rebooting:
sudo systemctl restart joplin-sidecarnginx sits between the Cloudflare Tunnel and the Node server. Because Cloudflare handles TLS, nginx only needs to listen on port 80.
Copy the included config:
sudo cp notes.milescb.com.conf /etc/nginx/sites-available/notes.milescb.com
sudo ln -s /etc/nginx/sites-available/notes.milescb.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxor alternatively copy to /etc/nginx/conf.d depending on how you've setup nginx.
The config proxies all traffic to 127.0.0.1:3456:
server {
listen 80;
server_name notes.milescb.com;
location / {
proxy_pass http://127.0.0.1:3456;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_header Content-Type;
}
}In the Cloudflare Zero Trust dashboard, create a tunnel with a public hostname pointing to
http://127.0.0.1:80. No SSL certificates are needed on the server — Cloudflare terminates
TLS at the edge.
Public hostname: notes.yourdomain.com
Service: HTTP → 127.0.0.1:80
npm run devStarts the Astro dev server with hot-reload. Set environment variables before running
(the dev server reads process.env directly — no .env file loading is built in, so either export
them in your shell or use a tool like dotenv-cli).


