A shared password manager is only useful when teams trust the deployment around it. Passbolt is a good fit for engineering, support, and operations teams that need role-based credential sharing without handing every secret to a closed SaaS vault. This guide shows a practical Ubuntu deployment using Docker Compose for repeatable services, MariaDB for durable metadata, Caddy for automatic HTTPS, and Passbolt's GnuPG/JWT volumes for cryptographic material. The goal is not just to make the login screen appear; the goal is a service you can patch, back up, restore, and verify during an incident.
The examples use passbolt.example.com. Replace it with your real hostname before running commands. Keep the service private during the first boot, confirm email delivery, then invite users after backups and recovery notes are in place.
Architecture and flow overview
The public path is intentionally simple: browser traffic terminates at Caddy on ports 80 and 443, Caddy proxies the application to Passbolt on localhost port 8080, and Passbolt talks to MariaDB on the private Compose network. The database volume stores accounts, resources, permissions, and audit metadata. The GnuPG and JWT volumes store server-side cryptographic material that must be backed up with the database; losing one side makes recovery painful or impossible.
For production, keep the host minimal. Do not expose MariaDB to the internet, do not bind Passbolt directly to a public address, and avoid putting SMTP credentials in shell history. Treat this host like identity infrastructure: patch it, monitor it, and test restore procedures before the first real outage.
Prerequisites
- Ubuntu 22.04 or 24.04 LTS with a non-root sudo user.
- A DNS record such as
passbolt.example.compointing to the server. - Ports 80 and 443 reachable from the internet for certificate issuance.
- SMTP credentials for invitation and recovery emails.
- At least 2 vCPU, 2 GB RAM, and encrypted server storage if possible.
Step-by-step deployment
1) Install Docker, Compose, Caddy, and firewall basics
Start from a patched host and install only the packages needed for the runtime and reverse proxy. The firewall allows SSH, HTTP, and HTTPS; all application services remain bound to localhost or the Compose network.
sudo apt update
sudo apt 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 update
sudo apt 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 in your browser, select the command block and copy it manually.
2) Create the application layout and strong secrets
Place Passbolt under /opt/passbolt so the deployment has a predictable home. The files created here are sensitive because they include database and application secrets. Restrict permissions before any values are written.
sudo mkdir -p /opt/passbolt/{data/gpg,data/jwt,db,backups,caddy}
sudo chown -R root:root /opt/passbolt
cd /opt/passbolt
openssl rand -hex 32 | sudo tee .db-password >/dev/null
openssl rand -hex 32 | sudo tee .app-secret >/dev/null
sudo chmod 600 .db-password .app-secret
If the copy button is unavailable in your browser, select the command block and copy it manually.
3) Write environment values
The base URL must match the final HTTPS hostname exactly. SMTP must be correct before inviting users because Passbolt relies on email for registration and recovery flows. Put real SMTP credentials in the file and keep it out of backups that are not encrypted.
cd /opt/passbolt
DB_PASSWORD=$(sudo cat .db-password)
APP_SECRET=$(sudo cat .app-secret)
sudo tee .env >/dev/null <<EOF
APP_FULL_BASE_URL=https://passbolt.example.com
PASSBOLT_SSL_FORCE=true
PASSBOLT_REGISTRATION_PUBLIC=false
DATASOURCES_DEFAULT_HOST=db
DATASOURCES_DEFAULT_USERNAME=passbolt
DATASOURCES_DEFAULT_PASSWORD=${DB_PASSWORD}
DATASOURCES_DEFAULT_DATABASE=passbolt
[email protected]
EMAIL_TRANSPORT_DEFAULT_HOST=smtp.example.com
EMAIL_TRANSPORT_DEFAULT_PORT=587
[email protected]
EMAIL_TRANSPORT_DEFAULT_PASSWORD=replace-with-smtp-password
EMAIL_TRANSPORT_DEFAULT_TLS=true
SECURITY_SALT=${APP_SECRET}
EOF
sudo chmod 600 .env
If the copy button is unavailable in your browser, select the command block and copy it manually.
4) Define the Docker Compose stack
This stack uses the community Passbolt image and MariaDB. Passbolt is published only on 127.0.0.1:8080, which means Caddy is the only public entry point. The database password is provided as a Docker secret backed by a root-readable local file.
cd /opt/passbolt
sudo tee compose.yml >/dev/null <<'EOF'
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: passbolt
MARIADB_USER: passbolt
MARIADB_PASSWORD_FILE: /run/secrets/db_password
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- ./db:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -upassbolt --password=$$(cat /run/secrets/db_password) --silent"]
interval: 20s
timeout: 5s
retries: 10
passbolt:
image: passbolt/passbolt:latest-ce
restart: unless-stopped
depends_on:
db:
condition: service_healthy
env_file: .env
volumes:
- ./data/gpg:/etc/passbolt/gpg
- ./data/jwt:/etc/passbolt/jwt
ports:
- "127.0.0.1:8080:80"
command: ["/usr/bin/wait-for.sh", "-t", "0", "db:3306", "--", "/docker-entrypoint.sh"]
secrets:
db_password:
file: ./.db-password
EOF
If the copy button is unavailable in your browser, select the command block and copy it manually.
5) Configure Caddy and start HTTPS
Many Caddy installations include /etc/caddy/sites-enabled from a base configuration. If yours does not, either add an import line to the main Caddyfile or place this site block directly in /etc/caddy/Caddyfile. Reload Caddy only after formatting succeeds.
sudo tee /etc/caddy/sites-enabled/passbolt.caddy >/dev/null <<'EOF'
passbolt.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
sudo caddy fmt --overwrite /etc/caddy/sites-enabled/passbolt.caddy
sudo systemctl reload caddy
If the copy button is unavailable in your browser, select the command block and copy it manually.
6) Start Passbolt and create the first administrator
After the containers are healthy, create the initial administrator and open the registration link from a trusted workstation. Store the administrator recovery kit offline. A second administrator is strongly recommended before normal users are onboarded.
cd /opt/passbolt
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f --tail=120 passbolt
If the copy button is unavailable in your browser, select the command block and copy it manually.
cd /opt/passbolt
sudo docker compose exec passbolt su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt register_user -u [email protected] -f Admin -l User -r admin" www-data
If the copy button is unavailable in your browser, select the command block and copy it manually.
Configuration and secrets handling best practices
Use a dedicated mailbox such as [email protected] and configure SPF, DKIM, and DMARC for reliable delivery. Invitation emails that land in spam will look like an application outage to new users. Rotate SMTP credentials if an operator leaves, and keep a short runbook for where those credentials are stored.
Back up the database, GnuPG material, JWT keys, Compose file, and environment file as a single recovery set. Encrypt off-server backups before uploading them to object storage. Do not rely on VM snapshots alone because snapshots rarely prove application-level consistency, and they are often retained in the same failure domain as the running service.
sudo tee /usr/local/sbin/backup-passbolt >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
DEST=/opt/passbolt/backups/$STAMP
mkdir -p "$DEST"
cd /opt/passbolt
docker compose exec -T db mariadb-dump -upassbolt --password="$(cat .db-password)" passbolt > "$DEST/passbolt.sql"
tar -C /opt/passbolt -czf "$DEST/passbolt-files.tgz" data .env compose.yml .db-password .app-secret
find /opt/passbolt/backups -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +
EOF
sudo chmod 750 /usr/local/sbin/backup-passbolt
sudo /usr/local/sbin/backup-passbolt
sudo crontab -l 2>/dev/null | { cat; echo '17 2 * * * /usr/local/sbin/backup-passbolt >/var/log/passbolt-backup.log 2>&1'; } | sudo crontab -
If the copy button is unavailable in your browser, select the command block and copy it manually.
For upgrades, read Passbolt release notes, take a fresh backup, pull images, and restart during a planned window. Confirm health checks and a test login before declaring the maintenance complete. If the service stores customer or production credentials, make the rollback plan explicit before touching versions.
Verification checklist
- DNS resolves to the expected server and Caddy presents a valid certificate.
- The Passbolt container reports healthy logs and can reach MariaDB.
- The first admin can complete registration and save the recovery kit.
- SMTP invitations arrive in a real inbox and do not land in spam.
- A backup directory contains both
passbolt.sqland encrypted file material. - A restore rehearsal has been documented on a non-production host.
curl -I https://passbolt.example.com
cd /opt/passbolt
sudo docker compose ps
sudo docker compose exec passbolt su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt healthcheck" www-data
sudo tail -n 80 /var/log/passbolt-backup.log 2>/dev/null || true
If the copy button is unavailable in your browser, select the command block and copy it manually.
Common issues and fixes
Caddy cannot issue a certificate
Check that DNS points to the server, ports 80 and 443 are reachable, and no other process is already listening. Certificate failures are almost always DNS, firewall, or duplicate proxy issues. Use sudo ss -lntp and the Caddy journal before changing Passbolt settings.
Passbolt shows the wrong domain or insecure links
Confirm APP_FULL_BASE_URL exactly matches the public HTTPS URL. After changing it, restart the application container and test a new invitation email. Mixed hostnames confuse browser security checks and user onboarding.
Email invitations never arrive
Verify SMTP host, port, username, password, and TLS mode. Also check provider-side restrictions: many mail services block new servers or require app-specific passwords. Send a test invitation to an external mailbox and inspect both Passbolt logs and mail provider logs.
Health checks complain about GnuPG or JWT material
Make sure the data/gpg and data/jwt directories are mounted and writable by the container. Do not delete and regenerate these files casually; they are part of the trust chain for the instance and must be recovered consistently with the database.
Backups complete but restores fail
Backups that are never restored are only hopeful archives. Schedule a quarterly restore test on a disposable host using a copy of the backup set. Confirm login, group visibility, and one shared credential before signing off.
FAQ
Can Passbolt run without HTTPS?
Do not run it for real users without HTTPS. Registration, recovery, and secret workflows depend on browser trust and secure transport. Use Caddy or another managed reverse proxy from day one.
Should MariaDB be hosted on the same server?
For small teams, colocating MariaDB is acceptable when backups and monitoring are solid. Larger environments may prefer managed database hosting, but network access should still be restricted to the application.
How should we handle user offboarding?
Remove the user, review shared resources they owned, rotate high-value credentials, and document the event. Password managers reduce chaos, but they do not remove the need for access reviews.
What should be monitored first?
Monitor certificate expiry, container health, disk usage, backup success, SMTP failures, and failed login spikes. These signals catch most operational problems before users report them.
Can we use an external secrets manager?
Yes. Keep the Compose pattern, but source database and SMTP secrets from your standard vault process during deployment. The important requirement is that operators do not paste long-lived secrets into tickets or shell history.
How often should we upgrade Passbolt?
Track security releases promptly and batch routine upgrades into planned maintenance windows. Always test from a fresh backup first, then upgrade production with a rollback point.
Internal links
- Deploy OpenBao with Docker Compose + Caddy + Integrated Raft
- Deploy Wazuh with Docker Compose + Caddy
- Deploy Forgejo with Docker Compose + Caddy + PostgreSQL + SSH
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.