Small operations teams often start with a shared spreadsheet for requests, implementation tasks, and client follow-up. That works until the board becomes the system of record and needs ownership, auditability, backups, and predictable access. Wekan is a lightweight open-source kanban application that can replace ad hoc tracking without forcing the team into a large project-management suite. This guide shows how to run Wekan on a single Ubuntu host with Docker Compose, MongoDB, Caddy-managed HTTPS, local-only application binding, backup routines, and practical recovery checks.
The deployment is intentionally conservative. Caddy terminates TLS on the host, Wekan only listens on 127.0.0.1, MongoDB stays on an internal Docker network, and secrets live outside the Compose file. That pattern mirrors the house style used in recent SysBrix Guides: start with a clear architecture, deploy with copyable commands, verify each layer, and finish with operational fixes instead of leaving the reader at a login screen.
Architecture and flow overview
The request path is simple: users open https://boards.example.com, Caddy obtains and renews the certificate, Caddy proxies traffic to Wekan on 127.0.0.1:8098, and Wekan talks to MongoDB over a private Docker bridge. Only ports 80 and 443 are exposed publicly. MongoDB has no host port, which reduces accidental database exposure and makes firewall policy easier to review.
For production use, treat the host as an application appliance. Keep the operating system patched, restrict SSH, monitor disk growth under /opt/wekan, and test backups before inviting users. Wekan boards are collaborative and operationally important; a clean restore process matters as much as the initial install.
Prerequisites
- Ubuntu 22.04 or 24.04 server with at least 2 vCPU, 2 GB RAM, and 20 GB disk.
- A DNS record such as
boards.example.compointing to the server. - Root or sudo access.
- Outbound internet access for Docker images and certificate issuance.
- SMTP credentials if you want invitations, password resets, and notifications.
Step-by-step deployment
1) Install Docker, Compose, Caddy, and firewall basics
Install Docker from the official repository, add Caddy from Ubuntu packages, and open only SSH, HTTP, and HTTPS. If your provider has a cloud firewall, mirror the same rules there.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg ufw
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
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-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin caddy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
If the copy button is unavailable, select the command block and copy it manually.
2) Create the application layout and a JWT secret
Keep all Wekan state under /opt/wekan. The JWT secret is generated once and mounted as a Docker secret so it does not appear in the Compose environment. Store it with restrictive permissions and include it in server backups.
sudo mkdir -p /opt/wekan/{mongo,backups}
sudo chown -R root:root /opt/wekan
cd /opt/wekan
openssl rand -hex 32 | sudo tee /opt/wekan/.jwt-secret >/dev/null
sudo chmod 600 /opt/wekan/.jwt-secret
If the copy button is unavailable, select the command block and copy it manually.
3) Write environment values
Replace the example domain and SMTP settings before starting the stack. ROOT_URL must match the public HTTPS address exactly; incorrect values commonly cause broken redirects, failed login callbacks, or invalid links in email notifications.
cat <<'EOF' | sudo tee /opt/wekan/.env >/dev/null
WEKAN_DOMAIN=boards.example.com
ROOT_URL=https://boards.example.com
MONGO_DATABASE=wekan
MAIL_URL=smtp://smtp-user:[email protected]:587/
MAIL_FROM=Wekan Boards <[email protected]>
WITH_API=true
BROWSER_POLICY_ENABLED=true
EOF
sudo chmod 600 /opt/wekan/.env
If the copy button is unavailable, select the command block and copy it manually.
4) Define the Docker Compose stack
The Compose file runs two services: MongoDB and Wekan. Notice the port mapping: Wekan is published only to 127.0.0.1:8098. That is required because the host-level Caddy process proxies to a host port; using expose alone would not make the application reachable from Caddy.
cat <<'EOF' | sudo tee /opt/wekan/docker-compose.yml >/dev/null
services:
mongo:
image: mongo:6
restart: unless-stopped
command: mongod --oplogSize 128
volumes:
- ./mongo:/data/db
networks:
- wekan-internal
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 30s
timeout: 10s
retries: 5
wekan:
image: wekanteam/wekan:latest
restart: unless-stopped
depends_on:
mongo:
condition: service_healthy
env_file: .env
environment:
MONGO_URL: mongodb://mongo:27017/wekan
PORT: 8080
JWT_SECRET_FILE: /run/secrets/wekan_jwt
ports:
- "127.0.0.1:8098:8080"
secrets:
- wekan_jwt
networks:
- wekan-internal
secrets:
wekan_jwt:
file: ./.jwt-secret
networks:
wekan-internal:
driver: bridge
EOF
If the copy button is unavailable, select the command block and copy it manually.
5) Configure Caddy for HTTPS
Caddy handles certificate issuance and renewal automatically. The security headers are intentionally modest because overly strict content security policies can break application assets or attachments if introduced without testing.
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.backup.$(date +%Y%m%d%H%M%S)
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
boards.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8098
header {
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
If the copy button is unavailable, select the command block and copy it manually.
6) Start Wekan and confirm first boot
Pull the images, start the services, and check both the local proxy target and the public HTTPS endpoint. The first boot may take a little longer while MongoDB initializes its data directory.
cd /opt/wekan
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
curl -I http://127.0.0.1:8098
curl -I https://boards.example.com
If the copy button is unavailable, select the command block and copy it manually.
Configuration and secrets handling best practices
Do not commit .env, .jwt-secret, or backup archives to a repository. For a small team, filesystem permissions and an encrypted server backup may be enough. For a larger environment, move SMTP credentials and the JWT secret to your existing secret manager, then render the files during deployment from CI/CD or configuration management.
Use a dedicated SMTP account with limited privileges and rotate it when staff changes. Keep WITH_API=true only if integrations need it; otherwise disable the API to reduce the exposed surface. Review Wekan administrator accounts quarterly, especially after contractors or clients leave a project.
Because MongoDB is embedded in this single-host deployment, disk monitoring is important. Board attachments, activity history, and long-lived archives can grow quietly. Add host monitoring for free disk space, failed systemd services, certificate renewal errors, and backup job freshness.
Backups and recovery routine
The backup routine below exports the Wekan database into compressed MongoDB archives and keeps fourteen days locally. In production, copy those files to object storage or another server. A local backup on the same disk is useful for mistakes, but it does not protect against host loss.
cat <<'EOF' | sudo tee /usr/local/sbin/backup-wekan >/dev/null
#!/usr/bin/env bash
set -euo pipefail
cd /opt/wekan
stamp=$(date +%F-%H%M%S)
mkdir -p backups
sudo docker compose exec -T mongo mongodump --archive --gzip --db wekan > "backups/wekan-${stamp}.archive.gz"
find backups -type f -name 'wekan-*.archive.gz' -mtime +14 -delete
EOF
sudo chmod +x /usr/local/sbin/backup-wekan
sudo /usr/local/sbin/backup-wekan
(crontab -l 2>/dev/null; echo '17 2 * * * /usr/local/sbin/backup-wekan >/var/log/backup-wekan.log 2>&1') | crontab -
If the copy button is unavailable, select the command block and copy it manually.
Test restore on a staging host at least once before relying on the backups. A restore drill verifies the archive format, MongoDB version compatibility, and the runbook your team will use during an incident.
cd /opt/wekan
sudo docker compose stop wekan
zcat backups/wekan-YYYY-MM-DD-HHMMSS.archive.gz | sudo docker compose exec -T mongo mongorestore --archive --gzip --drop
sudo docker compose start wekan
sudo docker compose logs --tail=80 wekan
If the copy button is unavailable, select the command block and copy it manually.
Verification checklist
After deployment, create a test user, create a board, add cards, upload a small attachment, log out, and confirm password-reset email delivery. Then run service-level checks so you know the proxy, application, and database layers all respond.
cd /opt/wekan
sudo docker compose ps
sudo docker compose logs --tail=100 wekan
sudo docker compose exec -T mongo mongosh --quiet --eval "db.adminCommand('ping')"
curl -fsS https://boards.example.com >/dev/null && echo "public HTTPS OK"
If the copy button is unavailable, select the command block and copy it manually.
- Public URL loads over HTTPS with a valid certificate.
- New user registration policy matches your organizationβs expectation.
- SMTP sends password reset or invitation emails successfully.
- Backups are created and can be restored on a test host.
- Only ports 22, 80, and 443 are reachable from the internet.
Common issues and fixes
Login redirects to the wrong host
Check ROOT_URL in /opt/wekan/.env. It must use the final public HTTPS URL, including the correct hostname. Update the file and restart Wekan with sudo docker compose up -d.
Caddy returns 502 Bad Gateway
Confirm Wekan is listening on the host port with curl -I http://127.0.0.1:8098. If that fails, inspect sudo docker compose logs wekan. If it works locally, validate the Caddyfile and reload Caddy again.
MongoDB health check never becomes healthy
Look for permission problems under /opt/wekan/mongo or disk exhaustion. MongoDB is sensitive to incomplete writes after a full disk event, so resolve storage pressure before repeatedly restarting containers.
Email invitations do not send
Recheck MAIL_URL, SMTP port, username encoding, and provider restrictions. Many mail providers require application-specific passwords or verified sender addresses. Test with a temporary board invitation after every SMTP change.
Uploads or boards disappear after redeploy
Make sure the Compose file still mounts ./mongo:/data/db and that you are running commands from /opt/wekan. Starting a copy of the stack from another directory can create a fresh empty data path.
FAQ
Can I run Wekan without Caddy?
Yes, but Caddy keeps certificate management and reverse proxying simple. If you already standardize on NGINX or Traefik, keep the same internal pattern: bind Wekan to localhost or a private network and expose only the reverse proxy publicly.
Should MongoDB be external?
For a small team, the embedded MongoDB container is straightforward and easy to back up. For heavier usage or strict database operations requirements, use a managed or separately administered MongoDB service and adjust MONGO_URL.
How often should I back up Wekan?
Daily backups are reasonable for many teams, but high-change environments may need hourly exports. Match the schedule to your recovery point objective and verify that off-host copies complete successfully.
Can I place Wekan behind SSO?
Wekan supports multiple authentication configurations depending on version and environment variables. Test SSO in staging first, document a break-glass administrator path, and keep local admin access available during identity-provider outages.
What should I monitor first?
Start with container health, HTTP availability, certificate expiry, disk utilization, backup age, and SMTP delivery. Those checks catch most practical outages before users report missing boards or failed invitations.
How do I upgrade safely?
Take a fresh backup, read Wekan release notes, pull images, restart during a maintenance window, and validate login, board loading, card edits, attachments, and email. Keep the previous image tag available for rollback instead of relying only on latest.
Internal links
- Deploy Kanboard with Docker Compose, Caddy, and PostgreSQL
- Deploy Vikunja with Docker Compose, Caddy, and PostgreSQL
- Deploy OpenProject with Docker Compose, Caddy, PostgreSQL, and Redis
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.
Header image: Unsplash, no watermark.