Skip to content

Going to Production

The quickstart configuration is built for a laptop. Everything in it that is convenient for local work is wrong for a deployment facing real users. This page is the list of what has to change.

Certificates

Replace the self-signed pair. A self-signed certificate is trusted only by parties you configure by hand. For the verifier, a wallet you do not operate will refuse a request signed with one; for the issuer, credentials you sign will not validate against another organization's trust anchors. You need certificates issued under a root the parties you deal with already recognize. See Signing certificates.

Know your expiry dates and rotate ahead of them. Neither service warns you. Rotating the verifier's certificate changes its client_id, since the identifier is a hash of the certificate — see Trust model.

Narrow your trust anchors. Each entry in the verifier's issuerTrustAnchors directory is a standing decision to accept every credential issued under it. See Issuer trust anchors.

Configure trust anchors. They decide which issuers the verifier accepts, and the issuer check does not run without them — a verifier started with none logs a warning at startup. Each anchor accepts every issuer under it, so keep the set small. See Issuer trust anchors.

Secrets

server.json contains the signing key path, the database password, and every API key. Treat the whole file as a secret.

  • Do not commit it. Render it at deploy time from your secret store, or mount it from one.
  • Do not reuse the examples. Every key, password and token printed in this documentation is a placeholder and should be assumed public. That includes api-key and postgres/postgres.
  • Generate API keys with a CSPRNG, one per calling application, so a single integration can be revoked on its own. See API keys.
  • Keep the signing key readable only by the service. It is mounted read-only in the quickstart; keep it that way, and keep it off shared volumes.

Image versions

Know which version you are running, and record it. The examples in this documentation pull latest:

image: registry.gitlab.com/secata/platform/did/release/verifier-backend:latest
image: registry.gitlab.com/secata/platform/did/release/issuer-backend:latest

latest moves, so two hosts pulling it a week apart can be running different code. Capture the version each host actually resolved — docker image inspect — as part of your deploy, so a bug report can name it.

Move the two together. The issuer and verifier are released independently but tested as pairs, so pull both in the same window rather than letting one drift ahead of the other.

Upgrades run database migrations. Back up the database first, upgrade one service at a time, and confirm it starts cleanly before moving the other. See Database.

Database

  • Real credentials, not postgres/postgres.
  • Separate databases for the issuer and the verifier. Sharing one makes their migrations collide.
  • Back it up. It holds your credential configurations and verification queries as well as in-flight sessions; losing it means recreating both by hand.
  • Do not publish the database port. The quickstart maps 5433 to the host for convenience; only the two backends need to reach it.

See Database.

Network exposure

baseUrl is what each service advertises to wallets, and it has to be an address a wallet on a mobile network can actually resolve — not localhost, and not a private address. Getting this wrong produces flows that start correctly and then fail silently when the wallet tries to follow up.

Both services must be reachable by wallets over HTTPS. Terminate TLS in front of them and make baseUrl the public HTTPS address, matching what the certificate covers.

The administrative endpoints — listed in API keys — should not be reachable from the public internet at all. Only the wallet-facing endpoints need to be.

Callbacks

Configure sessionCallback on both services so results are pushed to your application, rather than polling session status. Give the callback endpoint its own bearer token and verify it on every request: it is an unauthenticated inbound path into your application otherwise.

See Receive the issued credential and Receive verified data.

What's Next