What you will set up
Passbolt is a password manager for teams: the shared logins for the router, the bank, the web shop and the social media accounts, each shared with the colleagues who need it, as read-only, editable or owned. Every user has an OpenPGP key of their own, made and kept in their browser, and every password is encrypted for each person it is shared with before it leaves the browser. The server stores only encrypted data and cannot read the passwords. Since version 5, the names, usernames and addresses of the passwords are encrypted too.
Passbolt is made by Passbolt SA, a company in Luxembourg, and the Community Edition is open source under the AGPL 3.0 licence. A paid Pro edition adds account recovery, single sign-on and LDAP sync. People use Passbolt through its browser extension, for Chrome, Firefox, Edge, Brave and Safari, and there are apps for Android, iOS and Windows. The extension is not optional: the web page alone cannot sign in.
Here Passbolt runs as a pod under a user of its own called passbolt, with MariaDB for its data, behind Caddy from the Podman guide. It uses Passbolt's official non-root image, which runs as an ordinary user inside the container too.
Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13. We could not run the browser extension on the server, so we did what the extension does through Passbolt's API, with GnuPG and Passbolt's own command-line client, go-passbolt-cli:
- Passbolt CE 5.16.0 started with MariaDB 11.8, and its health check passed, with one known exception described under Troubleshooting.
- The first admin was created with Passbolt's command in step 5. Its setup link, and the invite for a second user, arrived by email, and both accounts were set up from those links with keys of their own.
- With encrypted metadata on, the admin created a password and shared it read-only with the second user, who could then read it. The database held only encrypted data for it, and an attempt by the second user to change a read-only password was refused.
- Public sign-up was closed. Passbolt logged each visitor's real address and ignored a forged
X-Forwarded-Forheader. - Emails went out through an SMTP server with STARTTLS and a password, from the secret in step 2. When the SMTP server was down, the invite waited in a queue and went out when it was back. With Swedish chosen for the organisation, invites were in Swedish.
- An update from Passbolt 5.15.0 to 5.16.0 went through
podman auto-update. Passbolt was restored from a backup into empty volumes, and everything came back by itself after a reboot.
The pod used about 200 MB of memory: 135 MB for MariaDB and 65 MB for Passbolt.
What we did not test: the browser extension's screens, the mobile and desktop apps, two-step login, and sending through a real email provider.
Before you start
You need:
- a server set up as in the Podman guide, with Caddy running;
- an A record and an AAAA record for
passbolt.example.compointing at your server; - an email account that can send through SMTP, at your email provider, for invites and notifications.
The examples use passbolt.example.com for Passbolt, 203.0.113.10 for your server's IPv4 address, which ip -brief address show eth0 shows, and smtp.example.com with the account passbolt@example.com for sending email. Replace them throughout.
1. Create the user
As root:
useradd -m -s /bin/bash passbolt
loginctl enable-linger passbolt
machinectl shell passbolt@
Everything up to step 4 runs as passbolt.
2. Create the secrets
openssl rand -hex 24 | tr -d '\n' | podman secret create passbolt-db-password -
printf %s 'the-smtp-password' | podman secret create passbolt-mail-password -
The first line makes the database password. The second stores the password of your email account, in place of the-smtp-password. Both are stored by Podman, not in the files below.
3. Define the pod
mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd
Create passbolt.pod:
[Pod]
PodName=passbolt
# Only Caddy, on this server, can reach Passbolt: the port is not open to the internet.
PublishPort=127.0.0.1:8139:8080
# Passbolt's health check calls its own address: send it to Caddy, not back into the pod.
AddHost=passbolt.example.com:host-gateway
[Install]
WantedBy=default.target
Create passbolt-db.container:
[Container]
ContainerName=passbolt-db
Image=docker.io/library/mariadb:11.8
Pod=passbolt.pod
Volume=passbolt-db:/var/lib/mysql
Environment=MARIADB_DATABASE=passbolt MARIADB_USER=passbolt MARIADB_RANDOM_ROOT_PASSWORD=1 MARIADB_AUTO_UPGRADE=1
Secret=passbolt-db-password,type=env,target=MARIADB_PASSWORD
HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=10s
Notify=healthy
AutoUpdate=registry
[Service]
Restart=always
And passbolt-app.container:
[Unit]
Requires=passbolt-db.service
After=passbolt-db.service
[Container]
ContainerName=passbolt-app
Image=docker.io/passbolt/passbolt:latest-ce-non-root
Pod=passbolt.pod
Volume=passbolt-gpg:/etc/passbolt/gpg
Volume=passbolt-jwt:/etc/passbolt/jwt
Environment=APP_FULL_BASE_URL=https://passbolt.example.com PASSBOLT_SSL_FORCE=true
Environment=PASSBOLT_SECURITY_PROXIES_ACTIVE=true PASSBOLT_SECURITY_PROXIES_TRUSTED_PROXIES=203.0.113.10
Environment=DATASOURCES_DEFAULT_HOST=127.0.0.1 DATASOURCES_DEFAULT_DATABASE=passbolt DATASOURCES_DEFAULT_USERNAME=passbolt
Secret=passbolt-db-password,type=env,target=DATASOURCES_DEFAULT_PASSWORD
Environment=EMAIL_TRANSPORT_DEFAULT_HOST=smtp.example.com EMAIL_TRANSPORT_DEFAULT_PORT=587 EMAIL_TRANSPORT_DEFAULT_TLS=true
Environment=EMAIL_TRANSPORT_DEFAULT_USERNAME=passbolt@example.com EMAIL_DEFAULT_FROM=passbolt@example.com EMAIL_DEFAULT_FROM_NAME=Passbolt
Secret=passbolt-mail-password,type=env,target=EMAIL_TRANSPORT_DEFAULT_PASSWORD
Environment=PASSBOLT_SECURITY_SMTP_SETTINGS_ENDPOINTS_DISABLED=true
AutoUpdate=registry
[Service]
Restart=always
What the settings do:
APP_FULL_BASE_URLis the address Passbolt puts in every link and email, and the one the extension checks it is talking to. It must be the publichttps://address.PASSBOLT_SSL_FORCEsends any plainhttp://request there.PASSBOLT_SECURITY_PROXIES_TRUSTED_PROXIESis the address Passbolt trusts to tell it the visitor's real address. Caddy's connections arrive in the pod from the server's own IPv4 address, as the Podman guide explains, so that is the one to name.- The
EMAIL_lines send email through your provider on port 587 with STARTTLS. Step 6 has the details.PASSBOLT_SECURITY_SMTP_SETTINGS_ENDPOINTS_DISABLEDhides the email settings in the admin pages, so that they cannot override these lines. - When it first starts, Passbolt makes the server's own OpenPGP key in the
passbolt-gpgvolume, and the key for the mobile apps' logins inpassbolt-jwt. The passwords, users and settings are in the database. - MariaDB stays on 11.8, a long-term release, and
MARIADB_AUTO_UPGRADEupdates its system tables after an update within it.
Start it:
systemctl --user daemon-reload
systemctl --user start passbolt-pod
systemctl --user enable --now podman-auto-update.timer
The first start downloads about 770 MB of images, and Passbolt takes a minute to set up its database. It is ready when this prints 302:
podman exec passbolt-app curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
4. Put Caddy in front
Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:
passbolt.example.com {
reverse_proxy 127.0.0.1:8139
}
Restart Caddy with systemctl --user restart caddy. Then go back to root, switch to machinectl shell passbolt@, and run Passbolt's health check:
podman exec passbolt-app bash -c 'source /etc/environment; /usr/share/php/passbolt/bin/cake passbolt healthcheck'
It may end with one error, "The /etc/passbolt/jwt/ directory should not be writable", which comes with the non-root image (see Troubleshooting). Any other [FAIL] line names what to fix. The check also asks GitHub which version of Passbolt is the latest, and fails if yours is older.
5. Create the first admin
Nobody can sign up on their own, so the first account is made with Passbolt's own command, as passbolt:
podman exec passbolt-app /usr/share/php/passbolt/bin/cake passbolt register_user -u you@example.com -f Anna -l Andersson -r admin
It prints a setup link, https://passbolt.example.com/setup/start/…, and sends the same link by email. The link works for 10 days.
Open it in your browser. Passbolt first asks you to install its extension, with Download extension. Then the extension walks you through the setup:
- It makes your OpenPGP key and asks for a passphrase to protect it. Choose a long one: it is the one password you will type from now on.
- It offers your recovery kit, a file with your private key. Store it safely, for example on a USB stick in a safe, and tick I safely stored my recovery kit. Without the kit and the passphrase, your account cannot be restored: the Community Edition has no account recovery.
- You pick a color and enter three characters as your security token. The extension shows them on every page where you type your passphrase, so you can tell a real Passbolt prompt from a fake one.
Then you are signed in. As the first admin, your extension also creates the organisation's shared metadata key, which encrypts the names and addresses of passwords; we did that part through the API, as the extension would. Check under Administration > Encrypted Metadata that it is on.
6. Email
Passbolt sends email for invites, account recovery, and notifications when someone shares, changes or deletes a password you have access to. It cannot work without it: a new user can only start through their invite link. To check the settings from step 3, as passbolt:
podman exec passbolt-app /usr/share/php/passbolt/bin/cake passbolt send_test_email --recipient=you@example.com
It shows the conversation with your SMTP server and ends with "The message has been successfully sent!". The other emails go through a queue, which Passbolt sends every minute. If your SMTP server cannot be reached:
- creating a user still works, but the invite does not arrive, and the user has no way to start;
- a user who has lost their extension, for example on a new computer, gets no recovery email;
- nobody hears about passwords that are shared with them or changed.
Passbolt keeps the email in its queue and tries again, so nothing is lost once sending works. To see what went wrong, as passbolt:
podman exec passbolt-db sh -c 'mariadb -upassbolt -p"$MARIADB_PASSWORD" passbolt -e "select email, subject, sent, error from email_queue order by created desc limit 5"'
Port 587 with EMAIL_TRANSPORT_DEFAULT_TLS=true uses STARTTLS. For port 465, which uses TLS from the start, set EMAIL_TRANSPORT_DEFAULT_HOST=ssl://smtp.example.com and EMAIL_TRANSPORT_DEFAULT_PORT=465, and remove EMAIL_TRANSPORT_DEFAULT_TLS=true. After a change, run systemctl --user daemon-reload and systemctl --user restart passbolt-app. We tested sending with STARTTLS and a password to a test mail server on the server itself, not through a real email provider, and we did not test port 465.
Until email works, the command in step 5 is also how you add other users: it prints the link, which you pass on yourself. Use -r user for a user who is not an admin.
7. Invite colleagues and share a password
In Passbolt, under Users, create a user with their email address and name. They get an invite by email, and set up their account with the extension as in step 5. Admins manage users; a user who is not an admin only manages their own passwords.
To share a password:
- In Resources, choose Create and add the password with its name, address, username and password.
- Select it and choose Share.
- Under Share with people or groups, type a colleague's name, and choose what they may do: Can read, Can update or Is owner. Then Save.
Their extension shows the password from then on, and they get an email about it. Groups, under Users, let you share with a whole team at once. The extension also fills in logins on websites, and its generator makes new passwords.
Under Administration you can also switch on two-step login with TOTP, under Multi Factor Authentication, and set password policies. We did not test them here.
8. Use Passbolt in Swedish
Passbolt is translated into Swedish. Under Administration > Internationalisation, choose Svenska as the default language for the organisation and Save. Emails are then in Swedish too, for users who have not picked another language. Each user can choose their own language in their profile, under Manage account.
9. Back up
As passbolt:
umask 077
mkdir -p ~/backup
podman exec passbolt-db sh -c 'mariadb-dump -upassbolt -p"$MARIADB_PASSWORD" --single-transaction passbolt' > ~/backup/passbolt-db.sql
podman volume export passbolt-gpg > ~/backup/passbolt-gpg.tar
podman volume export passbolt-jwt > ~/backup/passbolt-jwt.tar
That is the database, with every encrypted password, user and setting, and the server's own keys. Keep the server key: every user's extension remembers it, and refuses to sign in to a server with a different one. The passwords in the backup stay encrypted for each user, so the backup alone does not reveal them, but keep it private all the same. Copy ~/backup to another machine, for example with restic.
The users' own keys are not on the server. Each user's recovery kit and passphrase are their part of the backup.
To restore into an empty pod, as passbolt, with the backup in ~/backup:
systemctl --user stop passbolt-pod
podman volume rm passbolt-db passbolt-gpg passbolt-jwt
podman volume create passbolt-gpg
podman volume import passbolt-gpg ~/backup/passbolt-gpg.tar
podman volume create passbolt-jwt
podman volume import passbolt-jwt ~/backup/passbolt-jwt.tar
systemctl --user start passbolt-db
podman exec -i passbolt-db sh -c 'mariadb -upassbolt -p"$MARIADB_PASSWORD" passbolt' < ~/backup/passbolt-db.sql
systemctl --user start passbolt-app
Starting only the database first loads the backup before Passbolt sets up an empty one. On a new server, create the two secrets in step 2 first; the database password can be a new one.
10. Update
podman auto-update checks once a day for new images of Passbolt and MariaDB, and restarts the pod when one has changed. Passbolt updates its database when it starts. To update now:
podman auto-update
The latest-ce-non-root tag follows every release of the Community Edition. To update by hand instead, replace it in passbolt-app.container with a version tag, such as 5.16.0-1-ce-non-root, remove the AutoUpdate=registry line, and change the tag when you want to update. Either way, keep your backups current, and read Passbolt's release notes before larger updates. The extension updates itself through the browser's store.
Troubleshooting
The health check fails with "The /etc/passbolt/jwt/ directory should not be writable". In the non-root image, the directory belongs to the user Passbolt runs as, so it can always write there. The keys in it work, and the mobile apps use them to sign in. You can ignore this one.
The health check says "Could not reach the /healthcheck/status with the url specified in App.fullBaseUrl". The AddHost line in passbolt.pod is missing or names another address. Fix it, then daemon-reload and restart passbolt-pod.
Invites do not arrive. Run the test email in step 6, and look at the error column of the queue.
A user has lost their passphrase or recovery kit. The Community Edition cannot restore the account. Delete the user under Users, invite them again, and share the passwords with them again. Passwords that only they had access to are lost.
The extension says the server key has changed. The server has a different OpenPGP key from the one the extension remembers, for example after a restore without passbolt-gpg.tar. Restore that volume from the backup.