> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Compile service

> Run rivet-server to authenticate hosts and compile their catalogs over HTTPS.

`rivet-server` is an HTTPS service that compiles catalogs for hosts. A host running [`rivet run` in server mode](/operations/agent#server-mode) sends its facts and a credential. The service verifies the credential, checks that the host may use the requested environment, compiles the environment's [release](/operations/distribution), and returns the catalog. The host then verifies the assets and applies the catalog itself. The service never changes managed hosts.

```sh
rivet-server --config /etc/rivet/server.hcl
```

The server configuration uses the same literal-only format as the [agent configuration](/operations/agent#settings), shown here as plain text. Keep it outside the published source directory.

## GCP instance identity

```text
listen = "0.0.0.0:8443"

source {
  uri = "gs://example-rivet-config/site"
}

tls {
  cert_file = "/etc/rivet/pki/server.pem"
  key_file  = "/etc/rivet/pki/server-key.pem"
}

authentication {
  type     = "gcp"
  audience = "https://rivet.example.com"
  projects = ["example-project"]
}

allow "managed-nodes" {
  principal    = "gcp:service-account:rivet-agent@example-project.iam.gserviceaccount.com"
  environments = ["production", "staging"]
}
```

This assumes port 443 on `rivet.example.com` forwards to the listener on 8443. The listener always uses TLS. The service's own storage credentials need read access to the source; they are independent of how hosts authenticate. S3 sources work too.

The service verifies the token's signature against Google's keys, along with its issuer, audience, lifetime, verified service-account email, project, and full instance identity. Each request gets:

* a **principal**, `gcp:service-account:<email>`, which allow rules match;
* a **node** identity, `gcp:instance:<project>:<zone>:<instance-id>`.

Authorizing a service account admits every VM that runs as it. Add `target_identity` to a rule (see [authorization](#authorization)) to bind it to one machine.

## mTLS

Keep `listen`, `source`, and `tls` from above, and replace the authentication and allow rules:

```text
authentication {
  type     = "mtls"
  ca_file  = "/etc/rivet/pki/node-ca.pem"
  crl_file = "/etc/rivet/pki/node-crls.pem"

  trusted_extensions {
    role        = "1.3.6.1.4.1.55555.1"
    environment = "1.3.6.1.4.1.55555.2"
  }
}

allow "web-node" {
  principal    = "mtls:uri:urn:rivet:node:web-01"
  environments = ["production", "staging"]
}
```

Here `ca_file` is the CA for client certificates. Each client certificate must contain exactly one URI SAN starting with `urn:rivet:node:`. That URI is the node identity, and the principal is `mtls:uri:` followed by the URI. TLS must terminate at `rivet-server`; certificate headers forwarded by a proxy are ignored.

`crl_file` is optional and enables revocation checks. Certificates, CAs, and CRLs load at startup, so restart the service to pick up new ones. Issuing and renewing client certificates is up to your deployment.

`trusted_extensions` is optional. It names certificate extension OIDs to expose as [trusted claims](/language/trusted-facts#mtls-claims), such as `trusted.claims.extensions.role`. Replace the example OIDs with ones from your own namespace. Values must be UTF-8 strings of at most 4 KiB, and at most 64 extensions can be mapped. Your CA must control these values: if it signs whatever role a host asks for, hosts can choose their own classification.

## OIDC

```text
authentication {
  type     = "oidc"
  issuer   = "https://identity.example.com"
  audience = "rivet"
  jwks_url = "https://identity.example.com/.well-known/jwks.json"
}

allow "workload" {
  principal    = "oidc:https://identity.example.com:rivet-agent"
  environments = ["production"]
}
```

The issuer must sign tokens with RS256 and publish keys at an HTTPS JWKS URL. Tokens must be valid for at most one hour. The principal and node identity are both `oidc:<issuer>:<subject>`. Keys are refreshed every five minutes. A token signed with an unknown key triggers an earlier refresh, at most once every 30 seconds. Configure the host's [token file](/operations/agent#server-mode) and keep it fresh with your workload identity system.

On AWS, use mTLS or an OIDC workload token to authenticate, and IAM credentials for storage reads. Rivet doesn't accept SigV4 requests or EC2 instance identity documents.

## Authorization

Requests are denied unless an `allow` rule matches. Each rule has a unique name, an exact `principal`, and the `environments` it may use. Add `target_identity` to bind a rule to one machine's hashed identity, copied from that host's facts (`target.identity`). That is an extra check on top of credential authentication, not a replacement.

Facts submitted by a host are inventory only. Configuration sees the verified identity through the separate [`trusted` namespace](/language/trusted-facts), which only the service can populate.

Source files, catalogs, and cached assets can all contain secrets in plain text. Protect the service host, its cache, and storage write access as carefully as host credentials.

## Signed asset delivery

By default, hosts download assets from storage with their own credentials. To remove that requirement, add:

```text
signed_assets         = true
signed_asset_ttl_secs = 900
```

The service then returns signed GET URLs, valid for `signed_asset_ttl_secs` seconds (1 to 3600, default 900). This needs an S3 or GCS source and service credentials that can sign URLs; on GCS, a service-account key or IAM signing permission. Every external asset must belong to the release, so arbitrary cloud objects can't be delivered this way.

If URLs expire before the host finishes downloading, the agent can ask once for fresh URLs for the same catalog. A refresh never picks up a newer release. Refresh grants last an hour and are lost when the service restarts. Signed URLs and tickets are never stored in caches, reports, or journals.

## Settings and limits

| Setting or limit                  | Value                                                                            |
| --------------------------------- | -------------------------------------------------------------------------------- |
| `listen`                          | Default `0.0.0.0:8443`                                                           |
| `cache_dir`                       | Default `/var/cache/rivet-server`; must be private and owned by the service user |
| `signed_assets`                   | Default `false`                                                                  |
| Configuration file                | At most 64 KiB                                                                   |
| Connections                       | 128 at once; 10-second TLS handshake; 90-second connection lifetime              |
| Concurrent compiles and refreshes | 4; extra requests get HTTP 429                                                   |
| Facts per request                 | 1 MiB of inventory and 64 KiB of trusted claims                                  |
| Refresh grants                    | 10,000 grants, 64 MiB total                                                      |

Configuration and PEM files follow the agent's [file permission rules](/operations/agent#settings). Changes to configuration, TLS material, allow rules, or CRLs take effect after a restart.

## Plugins

The service loads approved [plugin](/contributing/plugins) definitions and functions from `/etc/rivet/plugins.lock.json`, or the file passed with `--plugin-lock`. A release can require plugins, but can't install or approve them. Plugin hooks and functions run as the service user and must be pure. Hosts install their own runtime plugins separately; function-only plugins need nothing on the host, because results are compiled into the catalog.

## Protocol

The service exposes `POST /v1/compile` and `POST /v1/assets/refresh`. `rivet run` is the supported client, and the protocol is experimental. Compile responses carry `Cache-Control: no-store`. Service failures never trigger source-mode or offline fallback.

The repository's [distribution checks](/contributing/testing#distribution-checks) use local fixtures, so test cloud credentials, signing, and IAM policy in your own deployment.