Quickstart · Linux standalone
One binary, one systemd unit.
goca is a single executable with the console, migrations and the full
manual built into it (goca docs prints the manual with no
network and no separate download). Verified on Ubuntu 24.04 with
1 vCPU and 512 MB RAM: the whole all-in-one process uses about
19 MB RSS.
Two facts up front
Signed release packages are not published yet — you build from the source your download link delivers. Roadmap And this shape needs a PostgreSQL you provide — the installer says so and refuses to pretend otherwise. Embedded, supervised PostgreSQL is the appliance shape, and it is not built.
1 · Build
The PKCS#11 bridge makes the full build cgo-enabled and dynamically linked, so build on the target — or on a build host with a matching libc — rather than copying the binary between libc families.
# build on the target, or on a Linux build host with a compatible C toolchain
CGO_ENABLED=1 go build -trimpath -tags "pkcs11 netgo osusergo" \
-ldflags "-s -w" -o goca ./cmd/gocaNo source yet? Request a download link
2 · Install
# install (on the target, as root)
chmod +x goca
sh deploy/standalone/install.sh ./goca
The installer creates a goca system user,
/var/lib/goca, a hardened systemd unit, and
/etc/goca/goca.env — then stops and tells you to edit it.
Re-running never overwrites that file, because
replacing the master secret would orphan every wrapped CA key in the
database.
3 · Configure, migrate, start
Provision your external PostgreSQL first, then:
$EDITOR /etc/goca/goca.env # DSN + master secret, both say CHANGE-ME
timedatectl # a CA signs validity windows from the clock
# Apply the schema. The migrations are INSIDE the binary — no checkout, no
# psql, nothing else to copy. Idempotent, so it is safe to re-run and safe to
# put in whatever runs your upgrades.
goca migrate # reads GOCA_DB_DSN, or pass --dsn
goca migrate --list # what this binary carries, if you want to look
systemctl enable --now goca
journalctl -u goca -f
goca migrate needs a credential that may create tables —
the database owner is the simple answer. The runtime DSN in
goca.env can be the same one for a standalone install; the
two only need separating when you split roles.
The system then bootstraps its own PKI unattended —
goca-infra-root and ica-infra appear within
seconds — and the console is on https://<host>:8085
with the same self-signed first-boot certificate the
Compose quickstart explains. Sign in
with the bootstrap account and change its password immediately.
4 · Then firewall 9090-9094, because nothing else will
This is the one hardening step the standalone shape does not do for you,
and the reason is worth understanding rather than copying.
--roles all runs every role in one process, so the internal
calls (api → ra, api → issuer,
issuer → keysvc) resolve in-process: no
hop, no socket for those calls. That is a real reduction in attack
surface — and it is not the same as "there is no internal
wire". In-process resolution removes the client, not
the server: the internal listeners still bind — 9090
keysvc, 9091 issuer, 9092 ra,
9093 crlgen, 9094 notifier, on every
interface, because there is no bind-address option — and they
still serve the same signing-oracle, issuance and decision endpoints,
fronted only by X-GOCA-Caller, a label the caller writes
about itself. The Compose bundle neutralizes this with one network per
edge of the call graph; a single host has no equivalent, so it is your
firewall or nothing.
Two things bound the damage, and neither is a substitute for the
firewall: keysvc will not sign without a pre-existing
signing intent matching key, purpose, caller and a SHA-256 of
the exact bytes presented — and no HTTP surface anywhere can create an
intent. The RA's decision endpoint is the softer target, which is why
the port list matters more than the ranking.
GET /v1/status/transport reports what the transport
actually is on a running process — the honest way to check, rather than
assuming either posture.