Self-Hosted Gateway

The Grafbase Gateway provides a way to run a federated graph on premises in your own infrastructure. The gateway runs either by polling the latest graph from the Grafbase API, or by passing the federated schema which allows running the gateway without internet connectivity

The Grafbase Gateway can be installed by running the following command in a terminal:

curl -fsSL | bash

In this mode, the gateway fetches the current federated graph from the Grafbase platform. You create a federated graph in the Grafbase API, publish the subgraphs and the gateway always has the current graph running.

The gateway is started in hybrid mode by providing the graph ref and an access token:

GRAFBASE_ACCESS_TOKEN=token ./grafbase-gateway \ --config grafbase.toml \ --graph-ref graph@branch

Here the graph-ref points to a graph created in the Grafbase API, and the branch to a name of a branch in that graph or if left empty, the production branch of the graph is used.

The access token is created in the API in your organization settings, and must have at least the permission to read a graph.

The gateway polls for graph changes every ten seconds.

In this mode, we never call the Grafbase API, and you have to provide the federated graph SDL as a file.

The gateway is started in self-hosted mode by providing the schema argument:

./grafbase-gateway \ --config /path/to/grafbase.toml \ --schema /path/to/federated-schema.graphql \ --listen-address

The configuration is defined using the TOML file format, and has the following settings available:

[graph] path = "/graphql" introspection = false
  • path is the path in the URL where the GraphQL API is available. Defaults to /graphql.
  • introspection lets you to decide if GraphQL introspection is available or not. Defaults to false.
[network] listen_address = ""
  • listen_address changes the address the local bound to. Can be set in the configuration or through the command line interface with -listen-address. Can be either IPv4 or IPv6 address. Defaults to

The CSRF protection is needed if the graph is accessible over the internet with a browser. Combined together with the CORS settings, it lets you to decide how and from which address a browser can access the graph.

If enabled, a special header x-grafbase-csrf-protection: 1 is required in every request that is not OPTIONS. If the header is not found, the server returns 403 Forbidden.

This is not needed if the server is not accessed through a browser.

[csrf] enabled = true
  • enabled enables the CSRF protection. Defaults to false.

The CORS settings together with the CSRF protection are to prevent requests coming from unknown sources. The settings are only needed if the server is accessed through a browser.

[cors] allow_credentials = false allow_origins = [""] max_age = "60s" allow_methods = ["GET", "POST"] allow_headers = ["Content-Type"] expose_headers = ["Access-Control-Allow-Origin"] allow_private_network = false
  • allow_credentials either enables or disabled credentials to be sent to the server. Defaults to false.
  • allow_origins is either a list of domains the server can be accessed from, or “any” which allows access from any domain. If cors settings are enabled, defaults to no domains allowed.
  • max_age indicates how long the results of a preflight OPTIONS request can be cached. Defaults to none.
  • allow_methods gives a list of HTTP methods allowed to use when accessing the server. Can be either a list of HTTP methods as strings, or “any”. Defaults to none if CORS settings are enabled. A good default is to enable GET and POST to have all the features available from the gateway.
  • allow_headers is a list of headers allowed to be sent to the endpoint. If the cors settings are enabled, the default is no headers allowed. Can be set either to a list of headers, or “any” which allows any headers to be sent.
  • expose_headers lists the headers a preflight request can sent back to the client. If the CORS settings are enabled, defaults to no headers allowed. The setting can be a list of headers, or “any” for any header to be exposed.
  • allow_private_network allows requests from a private network outside of the internet. Defaults to false.
[tls] certificate = "/path/to/cert.pem" key = "/path/to/key.pem"

In some cases you might not want to run a reverse proxy such as nginx in front of the gateway. The gateway can serve HTTPS traffic by itself. For encryption we’re using the rustls crate, backed by the BoringSSL library. If the tls section is not defined in the configuration, the server runs in plain HTTP mode.

  • certificate path to the certificate file in PEM format.
  • key path to the private key file in PEM format.
[operation_limits] depth = 10 height = 12 aliases = 5 root_fields = 6 complexity = 2

See more details from the Operation Limits documentation.

The gateway currently supports JWT authentication, which can be set through the configuration:

[[authentication.providers]] [authentication.providers.jwt] name = "my-authenticator" [authentication.providers.jwt.jwks] url = "" issuer = "" audience = "my-project" poll_interval = 60 [authentication.providers.jwt.header] name = "Authorization" value_prefix = "Bearer "

See more details from the Federated Authorization documentation.

The headers can either be static or forwarded from the client.

[headers.Authentication] value = "Bearer asdf" [headers.Content-Type] forward = "content-type"
  • value is a static header value
  • forward is a name of the header passed to the subgraphs from the request

Header values can be defined in environment variables with a special syntax:

[headers.Authentication] value = "Bearer {{ env.BEARER }}"

The environment variables must be set before starting the gateway.

Certain options can be defined per subgraph.

[subgraphs.products] websocket_url = "" headers = { "Content-Type" = { forward = "Content-Type }, "Authentication" = { value = "Bearer {{ env.AUTHENTICATION_TOKEN }}" } }
  • websocket_url The URL to use for GraphQL websocket calls.
  • headers per-subgraph header overrides.

Uses an async scheduler that periodically runs tasks to export batches of spans.

# optional: true [telemetry] # the name of the application/service # # optional: false service_name = "grafbase-gateway" # optional: true [telemetry.tracing] # default: false # optional: true enabled = true # sample a given fraction of traces (0..=1) ## default: 0.15 (15% of traces will be exported) ## optional: true sampling = 1 # filter that controls the interest on spans to be exported # # default: grafbase=info,off (only spans with *target=grafbase* with level info are to be exported) # optional: true filter = grafbase=debug,hyper=debug,off # spans collection # optional: true [telemetry.tracing.collect] # the maximum events per span before discarding # default: 128 # optional: true max_events_per_span = 128 # the maximum attributes per span before discarding # default: 128 # optional: true max_attributes_per_span = 128 # the maximum links per span before discarding # default: 128 # optional: true max_links_per_span = 128 # the maximum attributes per event before discarding # default: 128 # optional: true max_attributes_per_event = 128 # the maximum attributes per link before discarding # default: 128 # optional: true max_attributes_per_link = 128 # OTLP exporter # # optional: true [telemetry.tracing.exporters.otlp] # default: false # optional: true enabled = true # optional: false endpoint = "http://localhost:1234" # grpc or http are the available protocols # default: grpc # optional: true protocol = "grpc" # export request timeout in seconds # default: 60 # optional: true timeout = 120 # otlp batch export configuration # optional: true [telemetry.tracing.exporters.otlp.batch_export] # the delay, in seconds, between two consecutive processing of batches # default: 5 # optional: true scheduled_delay = 10 # the maximum queue size to buffer spans for delayed processing. If the # queue gets full it drops the spans # default: 2048 # optional: true max_queue_size = 10 # the maximum number of spans to process in a single batch. If there are # more than one batch worth of spans then it processes multiple batches # of spans one batch after the other without any delay. # default: 512 # optional: true max_export_batch_size = 10 # maximum number of concurrent exports # default: 1 # optional: true max_concurrent_exports = 10 # tls configuration to use on grpc export requests # optional: true [telemetry.tracing.exporters.otlp.grpc.tls] # domain name against which to verify the server's TLS certificate # optional: true domain_name = "my_domain" # path to the pem encoded private key of the certificate. # It can be an absolute path or relative to the binary execution path. # optional: true key = "/certs/grafbase.key" # path to the X509 Certificate, in pem format, that represents the client identity to present to the server # optional: true cert = "/certs/grafbase.crt" # path to the X509 CA Certificate, in pem format, against which to verify the server's TLS certificate # optional: true ca = "/certs/ca.crt" # headers to send on grpc export requests. # values need to be valid ascii encoded strings # optional: true [telemetry.tracing.exporters.otlp.grpc.headers] header1 = "header1" # headers to send on http export requests. # values need to be valid ascii encoded strings # optional: true [telemetry.tracing.exporters.otlp.http.headers] header1 = "header1" # prints to stdout the spans to export # optional: true [exporters.stdout] # optional: true enabled = true # export request timeout in seconds # default: 60 # optional: true timeout = 10 # optional: true [exporters.stdout.batch_export] # same as grpc

The server is written in Rust, and consists from the following components:

  • The latest axum web server
  • Tokio runtime for async operation. Runs in multi-threaded mode and has one worker per detected CPU core.
  • Rustls for encryption, backed by the ring and BoringTLS libraries.
  • Reqwest HTTP client, backed by the Hyper HTTP client together with rustls, ring and BoringTLS for encryption.

When the server is started in hybrid mode, it validates the access token and tries to fetch the latest federated graph from our CDN. It does a request every ten seconds, but doesn’t fetch anything if we detect no changes to be made in the graph. When composing a new or existing subgraph successfully, the graph is updated and the next call to the API fetches it, serves all the existing requests and replaces the GraphQL engine with a new version.

An example docker-compose.yml:

version: '3' services: grafbase: image: restart: always volumes: - ./grafbase.toml:/etc/grafbase.toml environment: GRAFBASE_GRAPH_REF: 'graph-ref@branch' GRAFBASE_ACCESS_TOKEN: 'ACCESS_TOKEN_HERE' ports: - '5000:5000'
Was this page helpful?