Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

AutoJoplin – self-hosted Joplin Docker Deployment

An installer and runtime layout for Joplin Server behind NGINX with optional Let’s Encrypt, optional email (SMTP), and daily backups.


Table of Contents


What you get

  • Postgres 14 with a proper healthcheck.
  • Joplin Server (latest image) with:
    • Configurable APP_BASE_URL
    • Optional SMTP mailer (relay or mailbox) or disabled mail.
    • A Node-based healthcheck (works even in minimalist images).
  • NGINX reverse proxy, HTTP→HTTPS redirect, HTTP/2 enabled.
  • Let’s Encrypt (webroot) certificates (optional).
  • Backups: daily cron (optional), with pg_dump (custom + plain SQL) and config snapshots.
  • A Compose wrapper (compose.sh) so commands work whether your host has docker compose or legacy docker-compose.

Requirements

  • A Linux x86_64/arm64 host with outbound Internet.
  • DNS A/AAAA records for your --domain pointing to this host.
  • Open firewall ports:
    • 80/tcp for HTTP (and Let’s Encrypt HTTP-01 challenge)
    • 443/tcp for HTTPS
  • sudo privileges (for installing Docker)

The script will install Docker CE for most common distros (Debian/Ubuntu, RHEL/Rocky/Alma/Fedora, openSUSE/SLES, Arch).


Quick Start

Browse to the directory with joplin-deploy.sh and run one of the following.

Rootful (classic)

sudo bash joplin-deploy.sh \
  --domain joplin.example.com \
  --le-email you@example.com \
  --db-pass 'SuperSecret' \
  --enable-backup
  • Installs Docker CE + Compose plugin (if missing)
  • Sets up the stack under /opt/joplin-stack
  • Issues a Let’s Encrypt cert via webroot
  • Installs daily backup + renew cron
  • No email configuration

Minimal example (no LE, use your own certs):

sudo bash joplin-deploy.sh \
  --domain joplin.example.com \
  --no-letsencrypt \
  --db-pass 'SuperSecret' \
  --enable-backup
  • Then place certs:
    • /opt/joplin-stack/letsencrypt/live/joplin.example.com/fullchain.pem
    • /opt/joplin-stack/letsencrypt/live/joplin.example.com/privkey.pem
  • Reload nginx:
  • cd /opt/joplin-stack && ./compose.sh --env-file ./.env restart nginx

Mail relay configuration example:

sudo bash joplin-deploy.sh \
  --domain joplin.example.com \
  --le-email you@example.com \
  --smtp-mode relay \
  --smtp-host smtp-relay.gmail.com --smtp-port 587 --smtp-security starttls \
  --noreply-email noreply@example.com \
  --db-pass 'SuperSecret' \
  --enable-backup

Mail mailbox configuration example:

sudo bash joplin-deploy.sh \
  --domain joplin.example.com \
  --le-email you@example.com \
  --smtp-mode mailbox \
  --smtp-host smtp.gmail.com --smtp-port 587 --smtp-security starttls \
  --smtp-user noreply@example.com --smtp-pass 'APP_PASSWORD' \
  --noreply-email noreply@example.com \
  --db-pass 'SuperSecret'

Flags / Configuration

Key flags (full list in --help):

Flag Required Default Notes
--domain Public FQDN (A/AAAA must point to the host)
--db-pass ✅* autogenerated Postgres password. If omitted, a strong random password is generated and placed in .env
--le-email ☑️ Required if using Let’s Encrypt (default ON)
--no-letsencrypt off Disable LE (bring your own certs)
--smtp-mode none none | relay | mailbox
--smtp-host SMTP hostname (required for relay/mailbox)
--smtp-user / --smtp-pass Required for mailbox (and relay if your relay is authenticated)
--smtp-port 587 Typically 587 with starttls or 465 with tls
--smtp-security starttls starttls | tls | none
--noreply-email noreply@localhost From address used by Joplin
--enable-backup off Installs daily backup (02:30) and LE renew (03:05)
--no-cron off Skip all cron installation
--stack-dir /opt/joplin-stack Override deployment directory

Environment is written to ${STACK_DIR}/.env. The Compose file uses these variables for portability.


Directory Layout

Rootful (default): /opt/joplin-stack

joplin-stack/
├── .env
├── compose.sh                    # wrapper (docker compose / docker-compose)
├── docker-compose.yml
├── nginx/
│   └── conf.d/joplin.conf
├── letsencrypt/
│   └── live/<domain>/{fullchain.pem,privkey.pem}
├── data/                         # Postgres data directory
└── backups/
    ├── YYYY-MM-DD_HH-MM-SS/
    │   ├── postgres_joplin_*.dump
    │   ├── postgres_joplin_*.sql.gz
    │   ├── docker-compose.yml
    │   ├── .env
    │   └── nginx/...
    └── cron.log

Email (optional)

Email is disabled by default. To enable, choose one:

Relay (e.g., Google Workspace SMTP relay)

--smtp-mode relay \
--smtp-host smtp-relay.gmail.com --smtp-port 587 --smtp-security starttls \
--noreply-email noreply@example.com

Your relay may be IP-allowlisted (no auth) or require auth (then add --smtp-user/--smtp-pass).

Mailbox (single account, e.g., smtp.gmail.com)

--smtp-mode mailbox \
--smtp-host smtp.gmail.com --smtp-port 587 --smtp-security starttls \
--smtp-user noreply@example.com --smtp-pass 'APP_PASSWORD' \
--noreply-email noreply@example.com

In both cases, the script sets MAILER_ENABLED=1. If --smtp-mode none, MAILER_ENABLED=0 and all SMTP fields are harmlessly present but unused.


TLS / Let’s Encrypt

  • Webroot HTTP-01 challenge on port 80.
  • Certificates are stored under ${STACK_DIR}/letsencrypt/ and mounted into Nginx at /etc/letsencrypt.
  • Daily renew cron at 03:05 will run certbot renew and reload Nginx.

Bring-your-own certs: place files at:

${STACK_DIR}/letsencrypt/live/<domain>/fullchain.pem
${STACK_DIR}/letsencrypt/live/<domain>/privkey.pem

Then reload Nginx:

cd ${STACK_DIR} && ./compose.sh --env-file ./.env restart nginx

Backups

If --enable-backup is provided, the installer drops backup-joplin.sh and a cron entry (02:30). The script:

  • Runs pg_dump (custom format .dump) and a plain SQL (.sql.gz).
  • Copies docker-compose.yml, .env, and the entire nginx/ directory.
  • Writes checksums (SHA256SUMS) and prunes backups older than 14 days.

Run ad-hoc:

cd ${STACK_DIR}
bash backup-joplin.sh

Restore

From a custom dump (preferred):

# Recreate DB (if needed)
./compose.sh exec -T joplin_postgres sh -lc 'psql -U "$POSTGRES_USER" -c "DROP DATABASE IF EXISTS joplin; CREATE DATABASE joplin;"'

# Copy and restore
docker cp backups/<timestamp>/postgres_joplin_*.dump joplin_postgres:/tmp/restore.dump
./compose.sh exec -T joplin_postgres sh -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; pg_restore -U "$POSTGRES_USER" -d joplin --clean --if-exists /tmp/restore.dump && rm -f /tmp/restore.dump'

From the plain SQL:

zcat backups/<timestamp>/postgres_joplin_*.sql.gz \
 | ./compose.sh exec -T joplin_postgres sh -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql -U "$POSTGRES_USER" -d joplin'

Operations

Check status / logs

cd ${STACK_DIR}
./compose.sh --env-file ./.env ps
./compose.sh --env-file ./.env logs -f app
./compose.sh --env-file ./.env logs -f nginx

Upgrade Joplin Server

cd ${STACK_DIR}
./compose.sh --env-file ./.env pull app
./compose.sh --env-file ./.env up -d app

Change SMTP or domain

  1. Edit ${STACK_DIR}/.env (SMTP vars, DOMAIN, APP_BASE_URL is derived from DOMAIN via compose).
  2. If domain changes, issue new certs:
    cd ${STACK_DIR}
    ./compose.sh --env-file ./.env run --rm certbot certonly --webroot -w /var/www/certbot -d NEWDOMAIN --email you@example.com --agree-tos --no-eff-email
    ./compose.sh --env-file ./.env exec -T nginx nginx -s reload
  3. Redeploy app and nginx:
    ./compose.sh --env-file ./.env up -d app nginx

Security / Hardening

  • Use strong, unique --db-pass. The script autogenerates one if omitted.
  • Set appropriate MAILER_NOREPLY_EMAIL and use provider recommendations (app passwords for Gmail).
  • Consider restricting Nginx to TLS 1.3 only if all clients support it.
  • Keep backups encrypted at rest if storing off-host (add gpg/age in the backup script if needed).

Firewall / SELinux / AppArmor

  • Firewall: open 80/tcp and 443/tcp on the host. Example (UFW):
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
  • SELinux: if enforcing and you use rootful with host paths outside standard locations, you may need appropriate contexts for mounted dirs.
  • AppArmor: default profiles are fine; you can set custom profiles for Nginx/Postgres if required by your policy.

Troubleshooting

Healthcheck is Unhealthy

The app healthcheck uses Node’s net module to test 127.0.0.1:22300. If unhealthy:

  • Check app logs:
    cd ${STACK_DIR}
    ./compose.sh --env-file ./.env logs -f app
  • Verify Postgres connection details (DB_* in .env).
  • Ensure the database container is healthy and reachable as db:5432 inside the app network.

Nginx says 502 Bad Gateway

  • App probably isn’t listening yet or failing to start.
  • Confirm from within the Nginx container:
    cd ${STACK_DIR}
    ./compose.sh exec -T nginx sh -lc 'wget -qO- http://app:22300/ || echo FAIL'

Email not sending

  • Check Joplin’s admin emails page (/admin/emails) and the app logs.
  • Verify MAILER_ENABLED is 1 and SMTP settings are correct for your provider.

Uninstall / Full Cleanup

Warning: This removes containers, volumes, certs, and database data (if you also delete data/). Make backups first.

cd ${STACK_DIR}
./compose.sh --env-file ./.env down

# Optionally remove images/networks/volumes related to the stack
docker network prune
docker volume ls | awk '/joplin/ {print $2}' | xargs -r docker volume rm

# Remove directory (certs, DB, backups)
rm -rf ${STACK_DIR}

License

This installer and README are provided under the MIT License. Joplin Server and all container images are licensed by their respective authors.

About

automated deployment of joplin self hosted server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages