Skip to content

How to Deploy TeamPass on a VPS

Updated Aug 2026

verified on Ubuntu 26.04 · Aug 2026

Self-host TeamPass on your own VPS with Docker Compose and HTTPS — a shared team credential vault with folder ACLs, roles, and a full audit trail.

Before you start
  • A small VPS — 1 vCPU / 1–2 GB RAM covers the app and its MariaDB
  • A fresh Ubuntu 26.04 server with root/sudo SSH access
  • A domain you can point at the server — this is a credential store, so HTTPS is mandatory
  • Docker Engine + Compose installed (see the base guide below)

What TeamPass is

TeamPass is a shared credential vault built for IT teams rather than for individuals. It's organised as a folder tree with per-folder access rules: you define roles, attach folders to them, and people see exactly the branches they're entitled to. Every read, edit, and share is written to an audit trail. It's one of the longest-running projects in this category, and it runs on the boring, familiar PHP-plus-MariaDB stack that a sysadmin can reason about without learning anything new.

The mental model is worth getting right up front, because it's different from most things in this category. TeamPass is a web application you log into — there is no browser extension autofilling your logins, no mobile app syncing a personal vault. It's the place your team keeps the router password, the registrar login, the service account that three people need and nobody should own. That's a genuinely different job from a personal password manager, and TeamPass is good at it.

Which means: it's a poor fit for personal use. A single user gets a lot of folder administration in exchange for very little. If what you want is a vault that autofills your own logins on your own devices, Vaultwarden is the lighter and far more pleasant answer. TeamPass earns its keep when several people need controlled, audited access to the same credentials.

One thing to know before you invest: the encryption saltkey lives outside the database. Back up the database alone and you restore rows nobody can decrypt. That fact shapes the Backups section and it's the single most important thing in this guide.

Server sizing

TeamPass is genuinely small. PHP-FPM and MariaDB, a database measured in megabytes, and load that scales with concurrent users rather than with the number of secrets you store. A 1 vCPU / 1 GB box runs it; 2 GB gives you comfortable headroom and is what to pick if a dozen people will use it daily, since MariaDB's default buffer pool in the official compose file wants a slice of memory to itself.

Disk is a non-issue — 20 GB is generous unless you attach a lot of files to entries. The realistic budget is a couple of dollars a month, flat, for as many users as you have.

That said, don't chase the very cheapest box on the market. This is where your team's shared credentials live, and what you actually want is dependable uptime and real disk snapshots, not the last dollar of savings. A small Hetzner instance is the value pick, DigitalOcean gives you snapshots and monitoring in one dashboard, and Contabo is the option when you want more RAM per dollar than either. All three are at the end.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root deploy user, a ufw firewall, and unattended security updates. If not, work through Docker & Compose on Ubuntu first — it's the base layer for every app on this site.

Confirm the firewall exposes only what it should:

sudo ufw status verbose

OpenSSH, 80/tcp, and 443/tcp allowed, everything else denied. Ports 80 and 443 are for the reverse proxy; TeamPass's own port stays internal, and the MariaDB container is never published to the host at all.

Point DNS. Create an A record for your hostname (say passwords.example.com) pointing at the server's public IP and confirm it resolves:

dig +short passwords.example.com

The public URL goes into the configuration and into the installation wizard, so get the name live before you start.

Install TeamPass (Docker Compose)

TeamPass's official quick start lives in its own repository rather than in a standalone compose file, so the install starts with a clone:

# TeamPass — official compose quick start (app + MariaDB)
git clone https://github.com/nilsteampassnet/TeamPass.git
cd TeamPass/docker/docker-compose
cp .env.example .env   # set DB_PASSWORD and MARIADB_ROOT_PASSWORD

Now the detail that will otherwise cost you your first fifteen minutes.

The sample .env pins an image tag that Docker Hub no longer serves. Copy it as-is and docker compose up -d fails on an image pull, with an error that reads like a network problem rather than a missing tag. Clear the pin before you start anything:

sed -i 's/^TEAMPASS_VERSION=.*/TEAMPASS_VERSION=latest/' .env

That gets you a working install today. Once the stack is up, look at what tag you actually landed on (docker inspect teampass-app | grep Image) and pin .env back to that specific version — a floating tag on a credential store means an unrelated docker compose pull can move you across a major version without warning.

Then edit the rest of .env:

Open .env in your editor:

nano .env

The values that matter:

TEAMPASS_VERSION=latest
TEAMPASS_PORT=8080
TEAMPASS_URL=https://passwords.example.com

DB_NAME=teampass
DB_USER=teampass
DB_PASSWORD=a-long-random-password-you-generated
DB_PREFIX=teampass_

MARIADB_ROOT_PASSWORD=a-different-long-random-password

INSTALL_MODE=manual

Generate both passwords properly — openssl rand -base64 32 — and change them from the samples without exception. They are the only thing between a misconfigured firewall and your entire vault.

TEAMPASS_URL should be the final public HTTPS URL, not the IP you're about to use for the wizard. And INSTALL_MODE=manual is the default and the right choice here: you'll run the wizard in a browser and see what it's doing. (There's an auto mode that takes an admin password from .env instead — convenient for rebuilding a known-good install, unnecessary the first time.)

Bring it up:

docker compose up -d
docker compose logs --tail 50

Two containers start: teampass-app and teampass-db. The app waits on a MariaDB health check, so the first start takes a minute. The app publishes port 8080 on the host by default and MariaDB publishes nothing at all.

For a production install, restrict that publish to the loopback interface so only the reverse proxy can reach it. Add to the teampass service in docker-compose.yml:

    ports:
      - "127.0.0.1:8080:80"

and docker compose up -d again. You'll want the wizard reachable first, though — so if you're proxying with Caddy (next section), set up HTTPS before you run the wizard and do the whole thing over your real hostname.

HTTPS + domain (mandatory)

TeamPass is a web application where people type a master password into a login form and read plaintext secrets out of a browser tab. Over plain HTTP, every one of those is on the wire. There is no version of this deployment where HTTPS is optional.

The reverse proxy of choice here is Caddy — it fetches and renews Let's Encrypt certificates automatically with essentially no configuration. Follow Automatic HTTPS with Caddy for the full setup; the Caddyfile entry for TeamPass is one block pointing at the port published above:

passwords.example.com {
    reverse_proxy 127.0.0.1:8080
}

Caddy terminates TLS on 443, obtains the certificate on first request, renews it on its own, and forwards decrypted traffic to TeamPass on the loopback port. It also sets the X-Forwarded-Proto header the application needs to generate https:// links behind a proxy.

The wiring caveat depends on how you run Caddy. On the host, 127.0.0.1:8080 is exactly the port above and the block works as written. As a Docker container, 127.0.0.1 inside it is the container's own loopback — so either give the Caddy service network_mode: host, or attach Caddy to the teampass-network and proxy to the service name (reverse_proxy teampass:80), dropping the host publish entirely.

Confirm https://passwords.example.com serves the TeamPass login or install page with a valid certificate before you go on. Running the wizard over the right URL saves you correcting it afterwards.

First-run setup (the wizard)

Open https://passwords.example.com — or, if you haven't put the proxy up yet, http://SERVER_IP:8080. TeamPass presents a browser-based installation wizard; this is the whole first-run experience, and it's the friendliest part of the deploy.

The wizard walks through:

  • Server checks — PHP extensions, permissions, directories. The container image is built to satisfy these, so a red line here usually means a volume didn't mount.
  • Database connection — host db, port 3306, and the database name, user, and password you set in .env. The container's environment pre-fills these; confirm rather than retype.
  • Table prefixteampass_, matching DB_PREFIX.
  • The absolute URL of the installation — this must be your real https://passwords.example.com, not the IP.
  • The admin account password — your first and, for the moment, only user.

When it finishes, log in as admin with the password you just set.

Then do the three things the wizard doesn't do for you. First, change the admin account's email and enable a second factor on it. Second, create a normal user account for your own daily use and keep admin for administration — TeamPass's audit log is a lot more useful when the administrative account isn't also the one doing everyday work. Third, build the folder tree and the roles before you add anyone: TeamPass's whole access model is folders attached to roles attached to users, and retrofitting that structure onto a vault people are already using is noticeably more annoying than designing it once at the start.

Some deployments also want the installation directory removed after setup as a hardening step; the container's wizard handles its own state through the config volume, so what matters on your side is that the config volume persists — see the next section.

Hardening

  • Never publish the app port to the world. 127.0.0.1:8080:80, Caddy on 443, nothing else. Check with docker ps that neither the app nor MariaDB maps to 0.0.0.0.

  • Design roles before users. Folder ACLs are TeamPass's reason to exist. Give each role the narrowest branch of the tree that lets its people work, and prefer roles over per-user grants — it's the difference between an audit you can read and one you can't.

  • Turn on two-factor authentication. TeamPass supports a second factor per account; enable it on every administrator at minimum, starting with the one you just created.

  • Use the audit log. It's one of the few vaults in this category that records who read a credential, not just who changed one. That's only valuable if somebody looks at it occasionally.

  • Keep the host tight. SSH keys only, no password auth, ufw limited to 22/80/443, unattended security updates on. The application is only as safe as the box under it.

  • Pin the image tag once you know which one you're on, so upgrades happen when you decide they do.

Backups

Here is the thing that makes TeamPass backups different from most: the encryption saltkey lives outside the database. The secrets in MariaDB are encrypted against it. Back up the database alone and you restore a complete, intact set of rows that nobody — including you — can decrypt. Upstream flags this volume as critical for exactly this reason.

The compose file keeps state in named volumes, and a backup needs these:

  • teampass-sk — the saltkey. The one that matters most.
  • teampass-db — the MariaDB data.
  • teampass-config — install state (settings.php, the CSRF config) and the master key. Without it, TeamPass reinstalls itself on restart.
  • teampass-secrets, teampass-files, teampass-upload — secret material, plus any files and attachments users have added.

Dump the database with the stack running, then archive the volumes alongside it:

cd ~/TeamPass/docker/docker-compose

# Consistent SQL dump
docker compose exec db \
  mysqldump --user=root --password=YOUR_ROOT_PASSWORD --single-transaction teampass \
  > teampass-db.sql

# The volumes, via a throwaway container that can see them
docker run --rm \
  -v docker-compose_teampass-sk:/sk:ro \
  -v docker-compose_teampass-config:/config:ro \
  -v docker-compose_teampass-secrets:/secrets:ro \
  -v docker-compose_teampass-files:/files:ro \
  -v docker-compose_teampass-upload:/upload:ro \
  -v "$PWD":/backup alpine \
  tar czf /backup/teampass-volumes.tar.gz -C / sk config secrets files upload

Check your real volume names with docker volume ls first — Compose prefixes them with the project directory name, which is docker-compose if you followed the clone path above.

Then encrypt everything into one archive and get it off the box:

tar czf - teampass-db.sql teampass-volumes.tar.gz .env \
  | gpg --symmetric --cipher-algo AES256 \
      -o "teampass-$(date +%F).tar.gz.gpg"

Copy the .gpg file somewhere that survives this VPS dying — object storage, another machine, anywhere but here. Encrypt it without exception: the archive is your team's entire credential set plus the key that unlocks it, so a backup that leaks is worse than a database that leaks.

Automate it on a schedule, and then do the part everyone skips: test a restore. Bring up a throwaway stack, load the SQL dump, restore the volumes — the saltkey included — and confirm you can log in and read an existing entry. A restore that produces a login page but unreadable secrets is exactly the failure this section exists to prevent, and you want to discover it on a Tuesday afternoon rather than during an outage.

Upgrades

With a pinned tag, upgrading is a deliberate edit:

cd ~/TeamPass/docker/docker-compose
# back up first — see above, then set TEAMPASS_VERSION to the tag you want:
sed -i 's/^TEAMPASS_VERSION=.*/TEAMPASS_VERSION=latest/' .env
docker compose pull
docker compose up -d
docker compose logs --tail 20

TeamPass applies its own database updates on start, and depending on the version jump it may show an administrative update step in the web UI on first login after the upgrade — follow it there rather than looking for a command. Your data lives in the named volumes, so recreating the containers is safe; the containers are disposable, the volumes are not.

Read upstream's release notes before a major jump. This is a long-lived PHP application with a real schema, and the occasional release changes something worth knowing about in advance.

Troubleshooting

docker compose up -d fails pulling the image. The .env.example tag problem. Confirm TEAMPASS_VERSION in .env isn't still the sample value, set it to latest to get running, then pin it to whatever you actually landed on.

The app container restarts, or the wizard can't reach the database. The app waits on MariaDB's health check, so a loop here is usually a credentials mismatch. Remember that MariaDB only applies MARIADB_* environment values on a fresh data volume — if you changed a password after the first start, the simplest fix before you have real data is docker compose down -v and start again.

TeamPass asks to reinstall after a restart. The config volume isn't persisting. teampass-config holds settings.php and the install state; confirm it's mounted and that you didn't remove volumes with down -v at some point.

Login works over IP but links point at the wrong host. TEAMPASS_URL in .env, and the absolute URL you gave the wizard, both need to be the public HTTPS URL. Correct them and restart.

Secrets show as unreadable after a restore. The saltkey volume wasn't restored, or was restored from a different point in time than the database. This is the failure mode the Backups section is built around — restore teampass-sk and the database together, from the same archive.

The page loads but assets or redirects use http://. The proxy isn't passing X-Forwarded-Proto. Caddy does this by default; if you swapped in another proxy, that's the header to check.

Verification + next steps

You're done when you can: load https://passwords.example.com over a valid certificate, log in as your own non-admin account, see exactly the folders your role grants and no others, confirm the app port isn't reachable from outside the box, and produce an encrypted, off-box backup containing both the database and the saltkey volume that you have restored at least once.

From there it's a quiet system. Keep the tag pinned, keep the backups leaving the box, and glance at the audit log now and then — that log is most of the reason to run TeamPass instead of something lighter. Underneath it all you want a host with dependable uptime and real snapshots: a small Hetzner instance is the value pick, DigitalOcean is the low-effort option with snapshots in the same dashboard, and Contabo is where to look for more RAM per dollar. See Best VPS for self-hosting for the ranked picks.

Next steps

How to self-host TeamPassBest VPS for a Password ManagerAutomatic HTTPS with CaddyRun Claude Code with Ollama on Your Own VPSDeploy Coolify on a VPSHow to Deploy AnythingLLM on a VPSHow to Deploy authentik on a VPSHow to Deploy Beszel on a VPSHow to Deploy Bitwarden on a VPSHow to Deploy Checkmate on a VPSHow to Deploy docker-mailserver on a VPSHow to Deploy Gatus on a VPSHow to Deploy Gitea on a VPSHow to Deploy Grafana on a VPSHow to Deploy Headscale on a VPSHow to Deploy Healthchecks on a VPSHow to Deploy Home Assistant on a VPSHow to Deploy Immich on a VPSHow to Deploy Jan on a VPSHow to Deploy LibreChat on a VPSHow to Deploy LocalAI on a VPSHow to Deploy Mailcow on a VPSHow to Deploy Mailu on a VPSHow to Deploy n8n on a VPSHow to Deploy NetBird on a VPSHow to Deploy Netdata on a VPSHow to Deploy Nextcloud on a VPSHow to Deploy Next.js to a VPSHow to Deploy Ollama on a VPSHow to Deploy Open WebUI on a VPSHow to Deploy OpenHands on a VPSHow to Deploy Pangolin on a VPSHow to Deploy Passbolt on a VPSHow to Deploy Plausible Analytics on a VPSHow to Deploy Psono on a VPSHow to Deploy Stalwart on a VPSHow to Deploy Supabase on a VPSHow to Deploy Twenty CRM on a VPSHow to Deploy Uptime Kuma on a VPSHow to Deploy Vaultwarden on a VPSHow to Deploy wg-easy on a VPSHow to Deploy Zabbix on a VPSDocker & Compose on Ubuntu 26.04Building AI Workflows with n8nInstall Open WebUI with OllamaAdding AI-Powered Insights to Plausible AnalyticsBuilding AI-Powered Apps with Supabase and pgvector

Search SelfHost Atlas

Search apps, comparisons, guides, and categories.

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.