Quickstart · Docker Compose

Fifteen minutes to a verified CA.

At the end of this page you have a running goca, a changed admin password, a real CA hierarchy with a test certificate issued, and a verified audit chain. Time required: about fifteen minutes.

Request a download link

Prerequisites

  • Docker with Compose v2 — the only supported way to run the local bundle. (Building the binary yourself instead needs Go ≥ 1.24 plus a C toolchain for the PKCS#11 bridge: CGO_ENABLED=1 go build -tags pkcs11 ./cmd/goca.)
  • ~1 GB disk for images and the Postgres volume.
  • Nothing else. No external services, no internet access after the image build.

Step 1 — bring the stack up

Your approved download link delivers the evaluation bundle. Unpack it, then:

cd goca
docker compose up --build

This starts two containers (profile all-in-one, preset in .env): goca-db (PostgreSQL 16) and goca running every role in one process. A third short-lived container, goca-db-migrate, applies the schema first — services wait for it. When the log line starting role api-admin (management plane) on :8081 appears, the system is up.

No download link yet? Request one

What is now listening, and where

Every one of these ports serves HTTPS, from a certificate goca issues to itself (step 2).

AddressWhat
127.0.0.1:8085Admin console — start here
127.0.0.1:8081Management API (the console proxies into this)
:8080Issuance plane — the REST API is served here (also the ACME head's storage API)
:8087ACME directory endpoint(s)
:8083CRL distribution — GET /crl/{ca}/{generation}
:8082, :8084CMP / OCSP listeners — both answer health and their role menu; the CMP head has no message handler yet, and OCSP responses are not served in this release Roadmap

The scep, est, rest and emul-rest heads are designed and not built, so nothing binds :8088:8091.

Management stays on loopback

The management surfaces (8081, 8085) are published on loopback only, on purpose. Reach the console from another machine with an SSH tunnel (ssh -L 8085:127.0.0.1:8085 <host>) or by publishing it on a management network — never by rebinding them to 0.0.0.0. goca terminates its own TLS, so a reverse proxy in front is optional, not required.

Step 2 — the browser warning you should expect

At first boot goca stands up its own trust before anything else: it creates goca-infra-rootica-infra and the goca-service-tls template, then issues every listener a real certificate and hot-swaps it in — usually within a second of the database being up. Until that first swap the listeners serve an ephemeral self-signed bootstrap certificate, and your browser will warn. The warning is expected in exactly two situations: this bootstrap moment, and a fault the console's alert banner and Status → Components → Edge TLS will name. Certificates renew and hot-swap automatically from then on — no cron job, no restart, no proxy.

To make the warning go away for good, trust the infra root. Fetch it from the open endpoint:

curl -sk https://127.0.0.1:8081/infra/trust -o goca-infra.pem

Import it into your OS/browser trust store, or point tools at it (curl --cacert goca-infra.pem …). The endpoint serves the self-signed root (CN=GOCA Infrastructure Root) — that is the file to import, not the issuing CA. On Windows it belongs in Trusted Root Certification Authorities, not Enterprise Trust. Restart the browser afterwards; the store is read at start.

The commands below use curl -k (skip verification) for brevity, as the manual does; substitute --cacert goca-infra.pem once you have fetched the root.

Step 3 — first login: do these three things immediately

  1. Change the bootstrap password. Open https://127.0.0.1:8085 and sign in as admin / admin. This account exists so a fresh install is usable; the server logs a loud warning until its password changes. Change it now (console: Users → your identity, or POST /v1/identities/admin/password).
  2. Label the system. The console shows a configurable heatline — a colored bar with a label (PROD, TEST, ROOT CA, …) above every screen, including login, where it matters most. On a fresh instance the top bar shows a muted "Set environment…" badge; click it. Presets cover the common convention (red = production, blue = root, amber = staging, green = test). Set it on day one — it is the cheapest wrong-instance protection you will ever deploy.
  3. Set a real master secret before creating anything you intend to keep. GOCA_MASTER_SECRET is the root of software key protection: the KEK that wraps every CA private key is derived from it. The shipped compose file sets a well-known development value — fine for evaluation, unacceptable for anything real. A hierarchy created under the dev value is protected by a public string; treat every key and token created under it as public.

The master secret, in four rules

Set it via your secret mechanism, never committed. Set it before creating your hierarchy — changing it later makes every wrapped key unusable (that is the design: a stolen database without the secret is inert). Escrow it — losing it means losing every software-custody CA key. Give it only to the containers that must unwrap a CA key: the all-in-one, the API service and the key service; everything else runs without it.

Step 4 — create a real hierarchy and issue a test certificate

In the console: CAs → New CA — create a self-signed root (pick an algorithm; slh-dsa-sha2-192s is the conservative PQC root choice, ECDSA P-384 the classical one), then an issuing CA with issuerRef pointing at the root. For production hierarchies, prefer running CA creation as a ceremony (Ceremonies → plan → quorum approval → execute — the four-eyes flow with a hash-chained transcript); the direct API path below creates immediately and is fine for evaluation. Then issue a test leaf from the issuing CA (Certificates → Issue) and sign a first CRL for it.

The same via API (token from POST /v1/auth/login):

A="Authorization: Bearer $TOKEN"; API=https://127.0.0.1:8081
curl -sk -H "$A" -d '{"name":"root-g1","commonName":"Example Root CA G1","organization":"Example","algorithm":"ecdsa-p384","validityYears":20}' $API/v1/cas
curl -sk -H "$A" -d '{"name":"ica-tls","commonName":"Example TLS CA G1","organization":"Example","algorithm":"ecdsa-p256","validityYears":10,"issuerRef":"root-g1"}' $API/v1/cas
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout test.key -subj "/CN=test.example.internal" -out test.csr
curl -sk -H "$A" -d "$(jq -n --rawfile csr test.csr \
  '{csrPem:$csr, profile:"leaf", validityDays:90, dnsNames:["test.example.internal"]}')" \
  $API/v1/cas/ica-tls/issue
curl -sk -H "$A" -X POST $API/v1/cas/ica-tls/generations/1/crl
curl -sk https://127.0.0.1:8083/crl/ica-tls/1 -o ica-tls.crl   # relying-party view

The CSR is required for this workflow: it carries the subject key and goca never receives the private key. profile is leaf — the default — or sub-ca; a certificate template controls the result. The response returns certificatePem. (This sequence uses jq to build the JSON body.)

Step 5 — five green checks

A fresh install should pass all five:

docker compose ps                            # both services healthy
curl -sk -H "$A" $API/v1/status/components   # every role heartbeating
curl -sk -H "$A" "$API/v1/audit/verify?limit=0"  # → "status":"intact"
curl -sk -H "$A" $API/v1/audit/checkpoints   # ≥1 signed head after ~5 min
curl -sk -H "$A" $API/v1/status/edge-tls     # every listener "source":"issued"
  • Both services healthy in docker compose ps
  • Every role heartbeating on /v1/status/components
  • Audit chain verifies "status":"intact"
  • At least one signed audit head (or force one with POST /v1/audit/checkpoints)
  • Every listener serving an issued certificate on /v1/status/edge-tls

In the console, Status → Audit & Logs should show intact plus a green signed heads badge.

Sidebar — restarting the stack: three shapes, three costs

Every long-running service in the bundle carries restart: unless-stoppedthe database included. Measured on a laptop running the distributed profile; your numbers will vary, the ordering will not:

1 · Host reboot

Automatic, seconds. The unless-stopped policy restarts the existing containers directly — no dependency chain, no init jobs, no seed. Nothing to type. (Docker Desktop must itself start with the machine: Settings → General → "Start Docker Desktop when you sign in".)

2 · stop / start

~25 s to fully green. Same containers, clean 15 s drain on the way down. This is the way to pause and resume a stack. Services answer sooner than the status flips — the healthcheck probes every 10 s.

3 · up -d

~65 s to fully green. Recreation — the only path that runs the dependency chain (database healthcheck, migrations, role passwords, TLS init), and the only one you need after changing images or compose configuration.

What never to do

A blanket docker start across all containers (or a desktop-UI "start everything"). That re-runs every one-shot container — including the demo seed, which will happily load its fixtures into your live database — and revives retired containers, neither of which docker compose would ever do. If the goal is "the stack I had before", shape 2 is the whole answer.

Next steps

  • Go distributed. COMPOSE_PROFILES=distributed docker compose up --build — one container per role, same image, one network per edge of the call graph. This is the shape that makes the ra/issuer/keysvc split real.
  • Mount an HSM. Two read-only mounts on the keysvc container and a Pkcs11Module document — goca ships no vendor library and no vendor configuration, ever.
  • Learn the model. The operator training covers custody, the issuance split, and the audit chain in depth.
  • Evaluation only: seed a fake estate with docker compose run --rm goca-db-seed — all key material in the seed is fake digest bytes; nothing can sign. Never seed an installation you intend to use for real.

Ready to try it?

Free — download on request.

Request a download link