Installation
Deploy Keygate on your own server with Docker Compose or from source. HTTPS, backups, upgrades and what to check before you go live.
Keygate is a Go server that keeps everything in PostgreSQL. A small virtual server is enough for most products: it serves the API, the admin dashboard and the customer portal from the same process. This page covers putting it on a server you control and keeping it healthy. If you only want to try it, the quick start is faster.
Requirements
- A Linux server or VM with a public domain name pointing at it.
- Docker with Compose 2.24 or later, or Go 1.27 and Bun if you build from source.
- PostgreSQL. The compose file runs PostgreSQL 18 for you; you can also use a managed database.
- Optional: S3 compatible storage (AWS S3, Cloudflare R2, MinIO) if you want Keygate to host your app's releases, and Redis or Valkey if you run more than one instance.
Docker Compose
This is the recommended way to run Keygate. Download the two files into a folder:
mkdir keygate && cd keygate
curl -O https://raw.githubusercontent.com/tabloy/keygate/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/tabloy/keygate/main/.env.exampleEdit .env. At a minimum set JWT_SECRET and LICENSE_SIGNING_KEY to random values (openssl rand -hex 32 makes one) and BASE_URL to the address people will use, such as https://licenses.example.com. Then start it:
docker compose up -dEverything in .env reaches the container. A few values are set in the compose file itself and win over .env: the port, ENVIRONMENT=production and the database address, which points at the bundled PostgreSQL. To use your own database, change DATABASE_URL in docker-compose.yml and remove the postgres service.
The configuration reference lists every setting.
Build from source
If you would rather not use Docker, build Keygate from source. You need Go 1.27 and Bun:
git clone https://github.com/tabloy/keygate.git
cd keygate
cp .env.example .env
make build
./bin/keygatemake build builds the server into bin/keygate and the dashboard into web/dist. Start it from the repository folder: it reads .env, the database migrations in db/migrations and the dashboard in web/dist from the folder it starts in, and real environment variables take precedence over the file. Run it under systemd or another supervisor, with that folder as the working directory, so it restarts after a crash or a reboot.
HTTPS and your domain
Keygate listens on plain HTTP, port 9000 by default. Put a reverse proxy in front of it to handle HTTPS. With Caddy this is the entire configuration, certificates included:
licenses.example.com {
reverse_proxy localhost:9000
}Whatever you use, set BASE_URL to the public HTTPS address. Keygate builds links with it: the links in emails, the return address after Stripe Checkout, the webhook address it registers with Stripe and the update feed addresses shown in Update settings, which warns you when BASE_URL still points at localhost. A subdomain of your product's domain, such as licenses.yourapp.com, works well, because your customers see it in the portal and in emails.
Admin accounts
The owner account is created on the setup page the first time you open Keygate, see the quick start. Invite other admins from Team in the dashboard settings, or list their emails in ADMIN_EMAILS so they become admins the first time they sign in. Everyone signs in with a one time code sent by email, so set up email delivery before you invite anyone.
Backups
All state lives in PostgreSQL, apart from release files in object storage. Back up the database on a schedule. With the bundled database:
docker compose exec postgres pg_dump -U keygate keygate > keygate-backup.sqlKeep these secrets with your backups, somewhere safe and separate from the server:
LICENSE_SIGNING_KEYsigns the offline tokens your app checks. If it changes, apps that store the old public key reject every new token.RELEASE_KEY_ENCRYPTION_KEYencrypts release signing keys, email provider credentials and stored license keys. Without it they cannot be decrypted.JWT_SECRETsigns login sessions. Changing it only signs everyone out.
Upgrading
Read the release notes, take a backup, then pull the new image and restart:
docker compose pull
docker compose up -dDatabase migrations run on their own when the new version starts. The compose file follows latest, which is the easiest way to stay current. If you would rather upgrade only when you decide, replace latest with a version number from the release notes:
image: ghcr.io/tabloy/keygate:x.y.zRolling back
The previous version normally starts fine on a database the new one has migrated. If a release note says otherwise, restore the backup you took before upgrading.
Running more than one instance
Several Keygate instances can share one database behind a load balancer. Set REDIS_URL on all of them so rate limits are counted together; without it each instance keeps its own count, and three instances allow three times the configured limit. Background work such as reminder emails is claimed through the database, so two instances do not send the same email twice.
Before you go live
BASE_URLis your public HTTPS address and the site loads there.- The secrets are random, stored safely and included in your backups.
- Email works: send yourself a test from the email settings.
- A database backup runs on a schedule, and you have restored one at least once.
- If you sell through Stripe, a test purchase creates a license. See Selling with Stripe.
Last updated October 4, 2026