Setup
The project Makefile builds and runs all components through docker-compose.
Building the images takes time, particularly the Better Auth component used for authentication service database migrations: @better-auth/cli.
First, clone the project:
git clone https://github.com/openfort-xyz/opensigner.gitTo build the containers, run:
make build # or `make clean build` to remove old images and volumesTo run them, use:
make runThe components are configured through environment variables.
The docker-compose.yml file at the repository root lists every variable with its default value.
Service-specific defaults are in files such as auth_service/.env.example.
Required Environment Variables
These variables have no defaults. Docker Compose refuses to start without
them, which is deliberate: a credential that falls back to a default is a
credential every reader of this repository already knows. Copy .env.example to
.env and fill in each one.
| Variable | Used by | Description |
|---|---|---|
JWT_SECRET | Auth service | Application secret for the auth service. Signs session cookies and encrypts the JWKS signing key at rest. Minimum 32 characters; generate with openssl rand -base64 48. The service refuses to start if this is unset or left at a library default. |
SHARE_ENCRYPTION_KEY | Hot storage | AES-256 key for encrypting shares at rest. Exactly 64 hex characters (32 bytes). Generate with openssl rand -hex 32. |
POSTGRES_PASSWORD | PostgreSQL | Superuser password. |
AUTH_SERVICE_DB_PASS | Auth service | Postgres password the auth service connects with — usually the same value as POSTGRES_PASSWORD. |
HOT_STORAGE_DB_PASS | Hot storage | Postgres password hot storage connects with — usually the same value as POSTGRES_PASSWORD. |
COLD_STORAGE_DB_PASS | Cold storage | Postgres password cold storage connects with — usually the same value as POSTGRES_PASSWORD. |
Rotating JWT_SECRET
JWT_SECRET does more than sign tokens: the auth service uses it to encrypt the
JWKS private signing key stored in the jwks table. Changing the secret without
accounting for that key leaves the deployment in a broken or unsafe state, so
rotation is a two-step operation.
Treat the old signing key as exposed. Anything that could read the previous
secret — a configuration file, an environment dump, a database backup, a snapshot,
or a log — could also decrypt the signing key it protected. A key is only as
private as every copy of the secret that encrypted it, so rotate on any suspicion
that a secret was disclosed, and do not reuse a jwks row across secrets.
Because hot_storage releases a wallet key share to any holder of a validly
signed JWT, a signing key that can be recovered is equivalent to being able to
authenticate as any user.
Optional Environment Variables
These variables have sensible defaults for local development but should be configured for production:
| Variable | Default | Description |
|---|---|---|
ALLOWED_ORIGINS | http://localhost:7050,http://localhost:7051 | Comma-separated list of allowed CORS origins. Used by both the auth service and hot storage. |
BETTER_AUTH_BASE_URL | http://localhost:7052 | Public base URL of the auth service. |
POSTGRES_USER | postgres | PostgreSQL superuser name. |
TRUST_PROXY | false | Set to true only when the auth service sits behind a proxy that sets x-forwarded-for. Rate limiting is keyed on the client address, so trusting that header from an untrusted source lets a caller send a fresh value per request and sidestep the limit entirely. |
GOOGLE_JWT_AUDIENCE | unset | OAuth client ID accepted for x-auth-provider: google. While unset, hot storage rejects Google tokens rather than accepting any Google-signed token regardless of the client it was issued to. |
AUTH_JWT_ISSUER / AUTH_JWT_AUDIENCE | value of BETTER_AUTH_BASE_URL | Expected iss and aud claims that hot storage enforces on incoming JWTs. These are the auth service's externally visible base URL, which is why they cannot be derived from AUTH_SERVER_URL — that is the internal address used to fetch the JWKS. |
ALLOW_INSECURE_AUTH_SERVER | false | Permits fetching the JWKS over plaintext HTTP. Hot storage otherwise requires https for AUTH_SERVER_URL, because anyone able to rewrite that channel can substitute the key set and mint tokens for any user. Local development only. |
Each service also accepts database connection variables (DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_SSLMODE)
with defaults suitable for the Docker Compose setup. See docker-compose.yml for the full list.
Once you have everything running, head over to the Getting Started guide.
OpenSigner runs as a set of Docker Compose services. Generate the required SHARE_ENCRYPTION_KEY with OpenSSL (openssl rand -hex 32).
Related
- What is OpenSigner? — the architecture and trust model.
- Deployment scenarios — self-hosted vs managed setups.
- Migrate existing keys from an Openfort project.