Teams lose critical knowledge in scattered documents, stale Confluence pages, and private notebooks that walk out the door with departing employees. For a consultancy, that means repeating the same architecture decisions for every client. For a product company, it means onboarding new engineers into a black hole of tribal knowledge. Wiki.js is a modern, open-source wiki platform built on Node.js that gives teams a self-hosted, Git-backed knowledge base with a rich visual editor, granular access control, and deep authentication integrations. Unlike heavyweight enterprise wikis, Wiki.js is fast, modular, and designed to run happily in a container stack.
In this guide, we will deploy Wiki.js on a single Ubuntu host with Docker Compose, publish it through Caddy for automatic HTTPS, connect it to a PostgreSQL database for durability, configure SMTP for notifications, and set up automated backups. The result is a production-ready knowledge platform that your team can use to document runbooks, architecture decisions, API contracts, and onboarding paths.
Architecture and flow overview
The deployment uses three containers on a dedicated bridge network: Wiki.js, PostgreSQL, and Caddy. Wiki.js serves the application on an internal port. PostgreSQL persists pages, users, and configuration. Caddy terminates TLS, handles certificate renewal, and proxies requests to Wiki.js. Persistent data lives in named Docker volumes for the database and in a bind mount for Wiki.js uploads and configuration. A nightly cron job dumps the PostgreSQL database to an encrypted archive on the host.
When a user opens the wiki in a browser, the request flows through Caddy on port 443, is decrypted, and forwarded to the Wiki.js container on the internal Docker network. Wiki.js queries PostgreSQL for the requested page content, renders it through the selected editor engine, and returns the HTML. File uploads are written to the bind-mounted ./data directory. Caddy manages ACME challenges and automatically renews the TLS certificate before expiry. Git sync can optionally push every page change to a remote repository for off-site version history.
- Caddy handles public HTTPS on port 443 and auto-redirects HTTP to HTTPS.
- Wiki.js runs the Node.js application server with the built-in visual and Markdown editors.
- PostgreSQL stores page content, user accounts, groups, and site configuration.
- Docker volumes persist the database files and uploaded assets across restarts.
- Host cron runs
pg_dumpnightly and rotates encrypted backups locally or to S3.
Prerequisites
- Ubuntu 22.04 or 24.04 LTS with SSH access and sudo privileges.
- A DNS A record pointing
wiki.yourdomain.comto the server public IP. - Docker Engine 24.x+ and Docker Compose plugin installed.
- Ports 80 and 443 open to Caddy for ACME challenges and HTTPS traffic.
- At least 2 GB RAM and 15 GB disk for uploads, search indexes, and backups.
- An SMTP provider account (SendGrid, Mailgun, AWS SES, or your own relay) for email delivery.
Step-by-step deployment
1) Install Docker, Compose, Caddy, and firewall basics
Update the package index, install Docker and the Compose plugin, and enable the firewall with only the ports Caddy needs:
sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
sudo apt install -y caddy
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
2) Create directories and environment file
Create a dedicated directory for Wiki.js, generate a strong database password, and prepare the environment file:
sudo mkdir -p /opt/wikijs/{data,backups}
sudo chown -R $USER:$USER /opt/wikijs
cd /opt/wikijs
DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
SESSION_SECRET=$(openssl rand -hex 32)
ADMIN_EMAIL="[email protected]"
cat > .env <
3) Define Compose services
The Compose file defines three services on a shared bridge network. Wiki.js loads environment variables from the external .env file and mounts the local data directory for uploads and configuration. PostgreSQL uses a named volume for durability. Caddy mounts the Caddyfile and its persistent certificate storage:
cat > compose.yml <<'COMPOSE'
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: wikijs
POSTGRES_USER: wikijs
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wikijs -d wikijs"]
interval: 10s
timeout: 5s
retries: 5
networks:
- wikinet
wiki:
image: ghcr.io/requarks/wiki:2
restart: unless-stopped
depends_on:
db:
condition: service_healthy
env_file:
- .env
environment:
DB_HOST: db
DB_PORT: 5432
volumes:
- ./data:/wiki/data
networks:
- wikinet
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- wikinet
volumes:
db_data:
caddy_data:
caddy_config:
networks:
wikinet:
driver: bridge
COMPOSE
4) Configure Caddy reverse proxy
Caddy handles TLS termination and proxies requests to the Wiki.js container. Create the Caddyfile with the reverse proxy block and security headers:
cat > Caddyfile <<'CADDY'
wiki.yourdomain.com {
reverse_proxy wiki:3000 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
CADDY
5) Start services and verify health
Pull the images, start the stack, and confirm that PostgreSQL and Wiki.js reach a healthy state:
docker compose pull
docker compose up -d
sleep 15
docker compose ps
docker compose logs --tail 50 wiki
6) Run first-time setup
Open https://wiki.yourdomain.com in a browser. Wiki.js will present a setup wizard on first launch. Create the administrator account, choose the default editor (Markdown or Visual Editor), and configure the site title and locale. After setup, log into the admin area and enable SMTP under Mail so the wiki can send invitations and notifications:
# Example SMTP settings in the Wiki.js admin panel:
# Host: smtp.sendgrid.net
# Port: 587
# Secure: STARTTLS
# User: apikey
# Password: your-sendgrid-api-key
# From Address: [email protected]
7) Backup script
Create a nightly backup script that dumps the PostgreSQL database and archives the Wiki.js data directory. Store the script in /opt/wikijs/backups/backup.sh:
cat > /opt/wikijs/backups/backup.sh <<'BKP'
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/opt/wikijs/backups"
DATE=$(date +%Y%m%d_%H%M%S)
source /opt/wikijs/.env
# Database dump
docker compose -f /opt/wikijs/compose.yml exec -T db pg_dump -U "$DB_USER" -d "$DB_NAME" | gzip > "$BACKUP_DIR/wikijs_db_$DATE.sql.gz"
# Data directory archive
tar czf "$BACKUP_DIR/wikijs_data_$DATE.tar.gz" -C /opt/wikijs data
# Rotate to keep last 14 days
find "$BACKUP_DIR" -name 'wikijs_*' -mtime +14 -delete
BKP
chmod +x /opt/wikijs/backups/backup.sh
# Schedule nightly at 02:00
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/wikijs/backups/backup.sh >> /opt/wikijs/backups/backup.log 2>&1") | crontab -
8) Acceptance checklist execution
Run through the checklist to confirm the deployment is production-ready:
- DNS resolves
wiki.yourdomain.comto the server public IP. curl -I https://wiki.yourdomain.comreturns HTTP 200 with HSTS headers.- The setup wizard completes without database connection errors.
- Upload an image to a test page and confirm it appears in
/opt/wikijs/data. - Invite a test user by email and confirm delivery.
- A backup archive exists in
/opt/wikijs/backups/after the first scheduled run.
Configuration and secrets handling
Never commit the .env file to version control. Store it in a secrets manager or encrypted backup. The SESSION_SECRET is used to sign session cookies; if it leaks, an attacker can forge sessions. Rotate it quarterly and force all users to re-authenticate after rotation. For SMTP, use an API key with send-only permissions rather than your primary mail password. Backup archives should be encrypted at rest if they are copied to object storage.
Consider running a separate Caddy instance or reverse proxy in front of the admin panel to add IP allowlisting. If you expose Wiki.js to the public internet, keep the admin path protected and monitor access logs for brute-force attempts. Enable two-factor authentication for the administrator account and require strong passwords for all users through the Authentication settings in the admin area.
Verification
- Run
curl -I https://wiki.yourdomain.comand confirm HTTP 200 withstrict-transport-securityheader. - Create a test page through the web editor and verify it renders correctly.
- Upload an attachment to the test page and confirm the file appears in
/opt/wikijs/data. - Log into the admin panel and verify the user list and site settings.
- Check
/opt/wikijs/backups/for a recent.sql.gzarchive. - Send a test invite to a secondary email address and confirm delivery.
Common issues and fixes
Setup wizard shows a database connection error: Ensure PostgreSQL has finished initializing before Wiki.js starts. The depends_on condition service_healthy handles this, but if you see repeated connection failures, run docker compose restart wiki after confirming docker compose logs db shows database system is ready.
Uploads fail with permission denied: The Wiki.js container runs as a non-root user. Ensure /opt/wikijs/data is writable by the container by setting ownership to 1000:1000 or by creating the directory before the first container start.
Caddy returns 502 Bad Gateway: Verify that the Caddyfile proxy target matches the Compose service name wiki on port 3000. Check docker compose logs wiki to confirm the application is listening on 0.0.0.0:3000 inside the container.
SMTP test emails are not delivered: Verify port 587 is open outbound, the SMTP host and credentials match your provider, and the sender domain has valid SPF and DKIM records.
Search indexing is slow or missing results: Wiki.js defaults to a basic search engine. For larger wikis, enable the PostgreSQL-based search module in Administration > Search Engine or connect an external Elasticsearch instance.
Certificate renewal fails behind a firewall: Caddy needs outbound HTTPS to the Let's Encrypt or ZeroSSL ACME servers. Whitelist acme-v02.api.letsencrypt.org and the relevant OCSP responders.
FAQ
Can I use MySQL or MariaDB instead of PostgreSQL?
Yes. Wiki.js supports MySQL, MariaDB, MS SQL Server, and SQLite. To use MariaDB, change the DB_TYPE to mariadb, update the Compose service to use the MariaDB image, and adjust the healthcheck accordingly. PostgreSQL is recommended for full-text search performance and long-term stability.
How do I migrate from Confluence or Notion?
Wiki.js supports bulk import from Confluence, Notion, GitHub Markdown files, and plain HTML. Use the Import feature in the admin panel and map user accounts after the content transfer. Large imports should be done during a maintenance window to avoid timeout errors.
Does Wiki.js support LDAP and SAML single sign-on?
Yes. Wiki.js includes built-in authentication modules for LDAP, Active Directory, SAML 2.0, OAuth2, and OpenID Connect. Configure these under Administration > Authentication and map group memberships to Wiki.js permissions for automatic role assignment.
Can I sync pages to a Git repository?
Yes. Enable the Git sync module in the admin panel and provide a repository URL with deploy-key credentials. Every page save will be committed to the remote repository, giving you a complete version history outside the database.
What happens if I lose the database?
Without backups, you cannot recover page content or user accounts. Restore from the latest pg_dump archive by stopping the stack, dropping the old database volume, recreating it, and piping the SQL dump into the new container. Re-index search after restoration.
How do I update Wiki.js?
Pull the latest image, recreate the container, and verify the version in the admin panel. Because the data directory and database are persistent, the update is non-destructive. Always take a backup before major upgrades.
docker compose pull wiki
docker compose up -d wiki
Can I restrict editing to specific groups?
Yes. Wiki.js uses a page-level and group-level permission system. Create groups under Administration > Groups, assign users, and set read, write, or manage permissions per page or namespace. You can also require authentication for all pages by disabling public access in the site settings.
Internal links
- Production Guide: Deploy Outline with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
- Production Guide: Deploy Penpot with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
- Production Guide: Deploy Grist with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.
Header image: Original SysBrix generated header, no watermark.