Joplin Server with Podman and Quadlets (2025 Edit)

Prepare Environment

The /tmp folder needs to be mounted on tmpfs (or ramfs…)

sudo systemctl enable --now tmp.mount

Open port in software firewall

sudo firewall-cmd --permanent --add-port 22300/tcp
sudo firewall-cmd --reload

Create joplin user and add subgid and subuid values. The range size below is probably not really necessary…

sudo useradd -m -c "Joplin Container User" joplin<br>sudo usermod --add-subuids 100000-165536 --add-subgids 100000-165536

Create central storage for sync data (adjust for your environment)

 sudo mkdir -p /appdata/joplin
 sudo chown -R joplin:joplin /appdata/joplin
 sudo chmod 2777 /appdata/joplin
 sudo semanage fcontext -a container_file_t "/appdata/joplin(/.*)?"
 sudo restorecon -Rv /appdata/joplin/

Login as joplin user. Note you can not use su here because it will require a login shell. Alternately you can

sudo machinectl shell joplin@
mkdir -p ~/.config/containers/systemd  # For Quadlet files
mkdir -p ~/cvols/postgres              # For database

Set up reverse proxy (optional) if you don’t want to expose the port

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/joplin;
}

Create Podman secrets via environment variables

export POSTGRES_PASSWORD='blah'
export POSTGRES_USER='blah'
export MAILER_AUTH_PASSWORD='blah'
export MAILER_AUTH_USER='blah'
podman secret create mailer_auth_password --env MAILER_AUTH_PASSWORD
podman secret create mailer_auth_user --env MAILER_AUTH_USER
podman secret create postgres_password --env POSTGRES_PASSWORD
podman secret create postgres_user --env POSTGRES_USER

Or Create Podman secrets using echo

echo -n 'blah' | podman secret create mailer_auth_password -
echo -n 'blah' | podman secret create mailer_auth_user -
echo -n 'blah' | podman secret create postgres_password -
echo -n 'blah' | podman secret create postgres_user -

Either of the methods run the risk of your password being in your shell history. Either clear your history when done, or configure your history to ignore echo and export lines, or ignore lines starting with a space and preface all commands with a space.

Quadlet Setup

Create three files in your ~/.config/containers/systemd folder

The jsync.network file contents (alter to suit your needs)

# jsync.network
[Network]
Subnet=192.168.30.0/24
Gateway=192.168.30.1
Label=app=joplin

The jsync_app.container file (adjust for your environment, per what you created above)
Note: Replace myserver, smtp_server with your server name and your smtp server name respectively.

# jsync_app.container
[Unit]
Requires=jsync_db.service
After=jsync_db.service

[Container]
Environment=APP_PORT=22300
Environment=APP_BASE_URL='http://myserver/joplin'
Environment=DB_CLIENT=pg
Environment=POSTGRES_DATABASE='joplin'
Environment=POSTGRES_PORT=5432
Environment=POSTGRES_HOST='myserver'
Environment=MAILER_ENABLED=1
Environment=MAILER_HOST='smtp_server'
Environment=MAILER_PORT=587
Environment=MAILER_SECURITY='starttls'
Environment=MAILER_NOREPLY_NAME='Joplin'
Environment=MAILER_NOREPLY_EMAIL='noreply@localhost'
Environment=STORAGE_DRIVER='Type=Filesystem; Path=/sync_data'
Environment=STORAGE_DRIVER_FALLBACK='Type=Database; Mode=ReadAndClear'
Image=docker.io/joplin/server:latest
PublishPort=22300:22300
Volume=/appdata/joplin:/sync_data:z
Network=jsync.network
Secret=postgres_password,type=env,target=POSTGRES_PASSWORD
Secret=mailer_auth_password,type=env,target=MAILER_AUTH_PASSWORD
Secret=mailer_auth_user,type=env,target=MAILER_AUTH_USER
Secret=postgres_user,type=env,target=POSTGRES_USER

[Service]
Restart=always

[Install]
WantedBy=multi-user.target default.target
```

The jysnc_db.container file (adjust per your environment per what you created above)

# jsync_db.container
[Container]
Environment=POSTGRES_DB='joplin'
Image=docker.io/postgres:16
PublishPort=5432:5432
Volume=/home/joplin/cvol/postgres:/var/lib/postgresql/data:z
Secret=postgres_password,type=env,target=POSTGRES_PASSWORD
Secret=postgres_user,type=env,target=POSTGRES_USER
Network=jsync.network

[Service]
Restart=always

Now update systemctl

systemctl --user daemon-reload
systemctl --user start jsync_app.service

If you get invalid origin error and are running selinux, you may need to

setsebool httpd_can_network_connect true