Quickstart

Stand up a self-hosted Proliferate server in about 15 minutes.

This guide takes you from a fresh Linux host to a working team install: the Proliferate server (the API and database your team connects to) running under Docker Compose, the instance claimed by you as its first admin, your desktop app connected and signed in, and your first teammate invited. Budget about 15 minutes of hands-on time, plus whatever your DNS takes to propagate. The Docker page covers the server pieces in more depth.

Provision a host

Before you start, make sure you have:

  • A Linux host with Docker and Compose v2 installed
  • A DNS name pointed at the host (for example proliferate.company.com)
  • Ports 80 and 443 reachable from your users, and from the internet so Let's Encrypt can issue certificates (also required later if you enable cloud sandboxes)

Fetch the deploy bundle

Every server release ships a small deploy bundle: the Compose file, the Caddyfile, and the bootstrap scripts. Replace X.Y.Z with the release you are installing (see the releases page):

curl -fsSL https://github.com/proliferate-ai/proliferate/releases/download/server-vX.Y.Z/proliferate-deploy.tar.gz | tar xz
cd proliferate-deploy
cp .env.production.example .env.static

Configure the environment

Edit .env.static. Three values matter for the base install:

# .env.static
SITE_ADDRESS=proliferate.company.com
PROLIFERATE_TELEMETRY_MODE=self_managed
PROLIFERATE_SERVER_IMAGE_TAG=X.Y.Z
 
# Leave these blank: bootstrap generates and persists them on first run
JWT_SECRET=
CLOUD_SECRET_KEY=
POSTGRES_PASSWORD=

SITE_ADDRESS is the hostname Caddy serves and requests certificates for; write it with or without a scheme (https is assumed when you leave it off). Bootstrap derives API_BASE_URL, the full public URL the server embeds in configuration it pushes to workspaces, from SITE_ADDRESS; set it explicitly only if your public URL differs. Pin PROLIFERATE_SERVER_IMAGE_TAG to the release you downloaded. Everything else is optional at this point; the full list is in the environment variable reference.

Bootstrap

./bootstrap.sh

bootstrap.sh generates any secrets you left blank, starts Postgres, runs database migrations, and brings the API up behind Caddy with automatic HTTPS. Once the health checks pass, it prints a one-time setup token and the claim URL:

First-run setup is pending. Claim this instance in a browser:

  Setup token: 4f9c1e...b27a
  Claim URL:   https://proliferate.company.com/setup

Screenshot: terminal, final bootstrap.sh output showing the setup token and claim URL

The tail of a successful bootstrap run, with the setup token and claim URL lines visible.

The token is never served over HTTP; it lives in a file inside the API container (/var/lib/proliferate/setup/setup-token) that only someone with shell access to the host can read, so a publicly reachable server is safe while it waits to be claimed. It stays available until you claim the instance; run ./wait-for-health.sh at any time to print it again.

Claim your instance

Open the claim URL in a browser and enter your email, a password, and the setup token. The organization name field is optional: leave it blank and the name is derived from your email domain (the derived default shows as the placeholder). Submitting creates your account, makes you the owner of the organization, and permanently closes open signup: after the claim, /setup returns 404 forever.

Screenshot: browser, the /setup claim page with email, password, optional organization name, and setup token fields

The server-rendered first-run claim form before submission, with the derived organization name shown as the placeholder.

Self-hosted servers run in single-org mode out of the box: SINGLE_ORG_MODE defaults to on whenever PROLIFERATE_TELEMETRY_MODE is not hosted_product, so there is exactly one organization and everyone you invite joins it. No configuration needed.

Connect the desktop app

Download the desktop app, then point it at your server by writing ~/.proliferate/config.json:

{
  "apiBaseUrl": "https://proliferate.company.com"
}

The file is read once at startup, so restart the app to apply it. Add "telemetryDisabled": true to the same file to turn off desktop telemetry for that install. See Connect the desktop app for rolling this out across a fleet.

Info:

In-product connect (a "connect to self-hosted server" entry on the sign-in screen) and a shareable proliferate://connect deep link are planned. Today the config file is the supported path.

Sign in

Open the app and sign in with the email and password you set at /setup. The desktop app asks your server which sign-in methods it offers (GET /auth/desktop/methods); when GitHub OAuth is not configured, the email and password form is the default. The base install needs no OAuth app at all.

Screenshot: desktop app, sign-in screen showing the email and password form against a self-hosted server

The default sign-in screen when the server has no GitHub OAuth configured.

To offer GitHub sign-in as well, create a GitHub OAuth app with callback URL https://proliferate.company.com/auth/desktop/github/callback, set GITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET, and restart the server. See GitHub sign-in.

Invite your team

In the desktop app, invite each teammate by email. Registration is invite-only and proven by a token: creating an account requires the invitation token from the invite link, and the server answers any unknown or mismatched token with the same 403.

Every pending invitation in the members list has a Copy invite link action. The link points at your server's registration page with the token and email prefilled:

https://proliferate.company.com/register?token=<invitation-id>&email=<email>

Share the link with your teammate. They open it in a browser, choose a password, and get an account in your organization. Then they install the desktop app, point it at your server with the same config.json, and sign in. If you configure email delivery (Resend is optional), the server emails the same link for you instead. Inviting the same email again issues a fresh link and the old one stops working.

Invitation roles are honored: invite someone as an admin and they are an admin the moment they register. Set ALLOWED_EMAIL_DOMAINS to additionally require that invited emails belong to your domains.

Screenshot: desktop app, organization members view with a pending invitation and its Copy invite link action

The members list after inviting a teammate, showing the pending invite with the Copy invite link menu item.

Info:

Set ADMIN_EMAILS in .env.static to guarantee admins: every listed email holds at least the admin role, asserted at every sign-in. It is also the lockout recovery path: add your email to the list, restart the server, sign in. Removing an email never demotes anyone, and the last admin can never be demoted or removed. The flip side: listed emails are reinstated as admin at their next sign-in even after removal, so to offboard someone on the list, remove them from ADMIN_EMAILS too and restart.

Updating

Updating is one command on the server:

./update.sh   # pull the new image, run migrations, restart

Your server reports its versions at GET /meta, and desktop update checks go through your server (GET /desktop/updater/latest.json redirects to the official signed build matching the version your server pins). Connected desktop apps follow the server, so updating the server updates the whole fleet. See Updates & versioning.

What's not here yet

Info:

Two honest limits of self-hosting today: automations and scheduled background jobs are not available yet (the self-hosted stack does not ship a worker tier), and the web app is not self-hostable in v1. Your team uses the desktop app.

Next steps

On this page