Skip to content

How to Deploy Infisical on a VPS

Updated Oct 2026

verified on Ubuntu 26.04 · Oct 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host Infisical on a VPS — the open-source secrets manager for apps and teams, run with PostgreSQL and Redis in Docker Compose behind Caddy HTTPS, with the encryption key handled properly, a restore-tested backup and a pinned-image upgrade path.

Before you start
  • A VPS with 4 GB RAM (the catalog's stated minimum for Infisical; the stack idles well below that, but the first start and migrations are heavier)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain or subdomain you can point at the server
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Infisical is

Infisical is an open-source platform for managing application secrets, certificates and privileged access. You store secrets per project and environment, read them from a web dashboard, a CLI, SDKs or a Kubernetes operator, and get audit logs and access controls around them. It is a TypeScript / Node.js application backed by PostgreSQL and Redis, and it is rated 2 / 5 to deploy.

One licensing point before you commit: the core is MIT-licensed, but code under the repository's ee/ directories is proprietary and needs a paid enterprise license. A self-hosted install runs fine without a license; the paid modules simply stay locked. If you need those features, read Infisical's terms first. For a lighter-weight option, see Vaultwarden, which stores personal passwords rather than application secrets.

This guide picks up from "you have a server" and ends with Infisical on HTTPS, a tested backup of the two things that matter (the database and the encryption key), and an upgrade routine. For help choosing a provider and tier, see Best VPS for self-hosting.

Server sizing

The catalog lists 4 GB of RAM as the minimum, and that is the safe size to buy. The catalog's own install measurement, on a 2-vCPU test box, put the running stack at roughly 1.4 GB of RAM and about 3.4 GB of disk. In this guide's verification run (a 2-vCPU, 8 GB test box, empty instance, idle after startup), docker stats showed about 720 MB for the Infisical backend, 190 MB for PostgreSQL, 5 MB for Redis and 15 MB for Caddy, roughly 0.95 GB in total before the operating system. The images came to about 3.4 GB on disk, the largest being Infisical's own, and the empty database about 150 MB. The first start runs database migrations and Node.js spikes while it does, and real use adds to both numbers, so a 2 GB server is cutting it close. Choose 4 GB unless you have a reason not to.

A secrets manager is not a place to be short of memory: if the kernel kills the backend, every deploy that reads a secret from it fails at the same time.

Prepare the server

Update the system and install the few tools this guide uses:

sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y curl openssl ufw dnsutils

If you're logged in as root, create a non-root sudo user and continue as that user:

adduser deploy
usermod -aG sudo deploy

Then open only SSH and the two ports the reverse proxy needs:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

Infisical will be reachable only through the reverse proxy. That matters on a Docker host: a published container port goes around ufw entirely, so the Compose file below binds the app to 127.0.0.1, and Caddy becomes the only public door. PostgreSQL and Redis publish nothing at all.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install Docker

If Docker isn't on the server yet, the convenience script installs Docker Engine and the Compose plugin (the Docker and Compose on Ubuntu guide covers the details):

command -v docker >/dev/null || curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
sudo docker compose version

The group change applies on your next login; until then the commands below use sudo docker.

Install Infisical

Infisical publishes a template .env file with sample secrets in it. The sample secrets are public, so they must be replaced before the first start. This block downloads the template from the same release as the image you are about to run, swaps in fresh random values, and sets the site address. Set your hostname first (this guide uses secrets.example.com; replace it with yours). The guard keeps the step safe to re-run: regenerating the encryption key on an existing install would make everything already stored unreadable.

mkdir -p ~/infisical && cd ~/infisical
DOMAIN=secrets.example.com
if [ ! -f .env ]; then
  curl -fsSL -o .env https://raw.githubusercontent.com/Infisical/infisical/v0.166.3/.env.example
  sed -i "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$(openssl rand -hex 16)|; s|^AUTH_SECRET=.*|AUTH_SECRET=$(openssl rand -base64 32)|; s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 16)|; s|^SITE_URL=.*|SITE_URL=https://$DOMAIN|" .env
  echo "DOMAIN=$DOMAIN" >> .env
  chmod 600 .env
fi
grep -c -E '^(ENCRYPTION_KEY|AUTH_SECRET|POSTGRES_PASSWORD|SITE_URL)=' .env

The ENCRYPTION_KEY in that file encrypts everything Infisical stores. If you lose it, the database is unreadable, and no backup of the database alone will save you. Copy .env to your password manager or offline storage now.

Now the Compose file. It is based on Infisical's own production example, with four changes: the image tag is pinned (the upstream file runs latest and its own comment says to pin it), the app binds to loopback, Caddy is added in front, and Redis gets a named volume. The app and the database read the same .env:

cd ~/infisical
cat > docker-compose.yml <<'EOF'
services:
  backend:
    image: infisical/infisical:v0.166.3
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    env_file: .env
    environment:
      NODE_ENV: production
    ports: ["127.0.0.1:8080:8080"]
  redis:
    image: redis:7-alpine
    restart: always
    volumes: ["redis_data:/data"]
  db:
    image: postgres:17-alpine
    restart: always
    env_file: .env
    volumes: ["pg_data:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s
      timeout: 10s
      retries: 10
  caddy:
    image: caddy:2
    restart: always
    ports: ["80:80", "443:443"]
    environment:
      DOMAIN: ${DOMAIN}
    volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "caddy_data:/data"]
    depends_on: [backend]
volumes:
  pg_data:
  redis_data:
  caddy_data:
EOF
cat > Caddyfile <<'EOF'
{$DOMAIN} {
    encode zstd gzip
    reverse_proxy backend:8080
}
EOF

Start it and wait for Infisical to answer. The first boot runs database migrations, so the loop below is bounded rather than open-ended:

cd ~/infisical
sudo docker compose up -d
code=000
for i in $(seq 1 60); do
  code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/api/status)
  [ "$code" = "200" ] && break
  sleep 3
done
echo "Infisical answered HTTP $code"
sudo docker compose ps

HTTP 200 and the db container healthy means the stack works. If it never answers, jump to Troubleshooting.

HTTPS + domain

Create an A record for your hostname (say secrets.example.com) pointing at the server's public IP, and confirm it resolves before Caddy tries to get a certificate:

dig +short secrets.example.com

Caddy is already running. It requests the certificate as soon as the name resolves and renews it on its own (see Automatic HTTPS with Caddy for how that works). Check that HTTPS answers, retrying while the certificate is issued:

. ~/infisical/.env
for i in $(seq 1 20); do
  out=$(curl -s -o /dev/null -w '%{http_code} %{ssl_verify_result}' "https://$DOMAIN/api/status") && [ "${out%% *}" = "200" ] && break
  sleep 5
done
echo "HTTPS status and certificate check: $out"

A second number of 0 means the certificate verified. Do this before creating the admin account, so the dashboard, invitation links and the CLI all use the https address from the start.

First login and hardening

Infisical has no default account. The first person to open the admin signup page becomes the instance administrator, so do it right after HTTPS works, before anyone else finds the address. Open https://secrets.example.com/admin/signup, enter your email and a long random password, and store it in your password manager. The instance reports whether that has happened yet:

. ~/infisical/.env
curl -s "https://$DOMAIN/api/v1/admin/config" | grep -o '"initialized":[a-z]*'

"initialized":false means nobody has claimed the instance; after you sign up it reads true. Creating the administrator itself was not tested in this guide's verification run, since it happens in the browser.

Then:

  1. In the Admin Console (the server administration area, not an organization), turn open signups off once your team has joined, or restrict them to your email domain. An open signup page on a secrets manager is an invitation.
  2. Create an organization and invite teammates by email, and use the machine-identity feature for applications rather than a personal login.
  3. Turn on two-factor authentication for every human account.
  4. Configure SMTP (the SMTP_* lines in .env, then sudo docker compose up -d) so invitations and password-reset emails reach people. Without it you have to hand out invitation links yourself. SMTP delivery was not tested in this guide's verification run, because it needs a live provider account.

Backups

Two things must survive: the PostgreSQL database and the .env file that holds the ENCRYPTION_KEY. A database backup without the key is unreadable, and the key without the database is worthless, so keep the pair together. Redis holds queues and caches rather than your secrets, so this guide does not back it up. Start with a database dump:

cd ~/infisical
sudo docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' \
  > ~/infisical-db-$(date +%F).sql
chmod 600 ~/infisical-db-$(date +%F).sql
test -s ~/infisical-db-$(date +%F).sql && ls -lh ~/infisical-db-*.sql

Then archive the configuration. This archive contains the encryption key and the database password, so the command restricts it to its owner:

cd ~/infisical
sudo tar czf ~/infisical-config-$(date +%F).tar.gz docker-compose.yml Caddyfile .env
sudo chmod 600 ~/infisical-config-*.tar.gz
ls -lh ~/infisical-config-*.tar.gz

Copy these files off the server, to encrypted object storage or another machine, and run the pair from cron. Because they hold the key to your secrets, treat the destination with the same care as the server itself. A backup you have never restored is a guess, so prove the dump loads. This starts a throwaway PostgreSQL container, imports the dump and counts the tables it created:

cd ~
sudo docker rm -f infisical-restore-test >/dev/null 2>&1
sudo docker run -d --name infisical-restore-test -e POSTGRES_PASSWORD=scratch postgres:17-alpine >/dev/null
for i in $(seq 1 40); do
  sudo docker exec infisical-restore-test pg_isready -h 127.0.0.1 -U postgres >/dev/null 2>&1 && break
  sleep 2
done
sudo docker exec infisical-restore-test createdb -U postgres infisical
sudo docker exec -i infisical-restore-test psql -q -U postgres -d infisical < ~/infisical-db-$(date +%F).sql >/dev/null
sudo docker exec infisical-restore-test psql -U postgres -d infisical -At \
  -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'"
sudo docker rm -f infisical-restore-test >/dev/null

It should print the number of tables, a number well above zero (it printed 769 in this guide's verification run). The check proves the dump loads and the schema is intact; it does not decrypt any secret, since that needs the key from your config archive. For a real restore onto a new server, install Docker, unpack the config archive into ~/infisical, start only the database (sudo docker compose up -d db), pipe the dump into it with sudo docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"', then start everything.

Upgrades

Because the image tag is pinned, an upgrade is an edit. Check Infisical's release notes, take a backup (previous section), change the tag in docker-compose.yml, then pull and recreate:

cd ~/infisical
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 20 backend

Infisical runs its database migrations automatically on the first start of a new version, and those are one-way, which is why the backup comes first. Releases are frequent, so move on a schedule you control rather than on every tag, and read the notes for anything that mentions migrations or configuration. Keep the postgres major version pinned too; a new PostgreSQL major is a dump-and-restore job, not a tag bump.

Troubleshooting

The dashboard returns 502 through Caddy, or never answers. Check sudo docker compose ps: if backend is restarting, read sudo docker compose logs backend. The usual cause is a database connection error, which happens when .env was regenerated after the database volume already existed with the old password. Restore the original .env, or remove the volumes if the install holds no data yet.

Everything is stored but unreadable after a restore. The ENCRYPTION_KEY differs from the one used when the data was written. There is no workaround; restore the original .env.

Caddy never gets a certificate. Confirm dig +short secrets.example.com returns the server's IP and that your provider's network-level firewall, not just ufw, allows inbound 80 and 443. A blocked port 80 silently breaks the ACME challenge.

The backend is killed or restarts under load. Run sudo docker stats and dmesg | grep -i oom. An out-of-memory kill means the server is too small for the stack; move up a tier rather than adding swap to a secrets service.

Invitations and password resets never arrive. SMTP is not configured or the provider rejects the sender. Fill in the SMTP_* lines and check sudo docker compose logs backend for delivery errors.

Verification + next steps

You're done when https://secrets.example.com loads with a valid certificate, you can sign in as the administrator, docker compose ps shows every service up after a reboot (restart policies bring them back), and you have restored a backup, with the key, onto a scratch box at least once.

From there, create a project and environments, add your first secrets, install the Infisical CLI on a workstation and point it at your own address, and wire a machine identity into one deploy pipeline. See the Infisical entry for catalog details and Best VPS for self-hosting for the ranked server picks.

Next steps

How to self-host Infisical →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AFFiNE on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy copyparty on a VPS →How to Deploy Directus on a VPS →How to Deploy Discourse on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Flarum on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghost on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy a GitHub Actions Self-Hosted Runner on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Invoice Ninja on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Kanboard on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy a Minecraft Server on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Odoo on a VPS →How to Deploy Ollama on a VPS →How to Deploy ONLYOFFICE Docs on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Plex Media Server on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy WordPress on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

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