Enable Orchestration
Stategraph Orchestration on a self-hosted server needs one variable to turn it on, public URLs, a GitHub App or a GitLab connection, a second logical database, and two PostgreSQL roles. It is off by default.
What the switch does
Set STATEGRAPH_ORCHESTRATION_ENABLED=true in the container environment. At each start, Stategraph then:
- Runs the Orchestration migrations on the
terrateamdatabase, and starts Orchestration. - Serves the Orchestration paths on port 8080:
/api/github,/api/gitlab,/api/v1/gitlab,/api/v1/github/kv,/api/v1/github/access-token, and/api/v1/access-token. They carry the VCS webhooks and callbacks, the runner API, the KV store, and access tokens. - Rebuilds the
postgres_fdwbridge from thestategraphdatabase to theterrateamdatabase. The console reads Orchestration data through it.
The paths, and so the webhook URLs, are the same as on a standalone Orchestration server.
Stategraph reads the switch only at a start with its default command. If you override command: or entrypoint:, the switch has no effect.
Variables
| Variable | Required | Notes |
|---|---|---|
STATEGRAPH_ORCHESTRATION_ENABLED |
yes | true or 1 |
TERRAT_API_BASE |
yes | Public URL plus /api, for example https://stategraph.example.com/api. The runner base URL derives from it |
TERRAT_UI_BASE |
yes | Public URL of the Terrateam console host. See Two consoles |
TERRAT_WEB_BASE_URL |
yes | Link base for a repository whose brand has no base of its own. https://app.terrateam.io when unset or empty |
STATEGRAPH_FDW_PASSWORD |
yes | Password of the stategraph_mql role. Without it, the server does not load its configuration |
GITHUB_APP_ID / GITHUB_APP_PEM |
for GitHub | The GitHub App that Orchestration runs as |
GITHUB_APP_CLIENT_ID / GITHUB_APP_CLIENT_SECRET |
for GitHub | The OAuth client of the App |
GITHUB_WEBHOOK_SECRET |
when GITHUB_APP_ID is set |
The webhook secret of the App |
GITHUB_APP_URL |
recommended | Install URL of your App, which the console links to. Without it, the console tells the user to install the App from the settings of the GitHub organization |
GITLAB_*, STATEGRAPH_FDW_PROVISIONER_PASSWORD |
for GitLab | See GitLab variables |
TERRAT_TELEMETRY_LEVEL |
no | anonymous (default) or disabled. Any other value stops Orchestration from starting |
Leave optional variables unset, not empty: some treat an empty string differently from an unset one. For example, an empty GITHUB_APP_URL discards the default and makes empty links.
Orchestration does not start without the GitHub webhook secret
When GITHUB_APP_ID is set, set GITHUB_WEBHOOK_SECRET to the webhook secret of the App. Unset, Orchestration would accept unsigned webhook events on the public path /api/github/v1/events. Empty, it would reject every webhook. So in both cases, Orchestration does not start, and only the container log shows the error: GITHUB_WEBHOOK_SECRET must be set when GITHUB_APP_ID is set.
Two consoles
With Orchestration on, the host of the URL selects the console:
| Host | Console |
|---|---|
STATEGRAPH_UI_BASE, or a host that Stategraph does not recognize |
The Stategraph console. Only the Orchestration paths in What the switch does go to Orchestration |
TERRAT_UI_BASE |
The Terrateam console. All paths under /api go to Orchestration |
Give the two variables different hosts, because a host serves a single console. When TERRAT_UI_BASE names the STATEGRAPH_UI_BASE host, or localhost, that host keeps the Stategraph console, and the container log says so.
The brand of a repository selects the host of its commit-check Details links and its pull request comments. A .stategraph/config repository uses STATEGRAPH_UI_BASE, and a .terrateam/config repository uses TERRAT_UI_BASE. When the variable of a brand is unset, its links use TERRAT_WEB_BASE_URL.
The Orchestration database
Orchestration keeps its own logical database, terrateam, on the same PostgreSQL server as stategraph. Its connection defaults to the DB_* values with the database name terrateam, so you usually do not set TERRAT_DB_*. TERRAT_DB_NAME never falls back to DB_NAME.
One of these creates the terrateam database:
- The
initdbscript, at the first start of a new volume, with the Docker Compose PostgreSQL. - Orchestration, at start, when the database is missing and the connecting role has
CREATEDB. - You, when the role does not have
CREATEDB, which is common on managed PostgreSQL. Orchestration logs the exact statement, waits 30 seconds, and tries again:
CREATE DATABASE "terrateam" OWNER "stategraph";
On PostgreSQL 15 and later, the public schema is closed to non-owners. So the role that Orchestration connects as must own the database, or its migrations fail. A volume from before the initdb script needs the CREATE DATABASE by hand, one time.
The FDW bridge and its roles
The bridge is a postgres_fdw server, user mapping, schema, and foreign tables. The migration at each start drops and rebuilds them from STATEGRAPH_FDW_HOST, STATEGRAPH_FDW_PORT, STATEGRAPH_FDW_DBNAME, STATEGRAPH_FDW_USER, and STATEGRAPH_FDW_PASSWORD. The host is where the PostgreSQL server reaches the terrateam database from itself. With both databases on one server, the defaults are correct: localhost, 5432, terrateam, and stategraph_mql.
Two roles have grants in the terrateam database. When a role is missing, Orchestration creates it NOLOGIN. After each migration, Orchestration makes the grants of the role agree with the catalog. It revokes before it grants, so a smaller catalog also revokes grants.
| Role | Use |
|---|---|
stategraph_mql |
The read role that the bridge authenticates as, with column-level grants on the catalog |
stategraph_provisioner |
The narrow write channel that the console uses to provision a GitLab installation. When STATEGRAPH_FDW_PROVISIONER_PASSWORD is unset, Stategraph does not build the admin bridge, the provisioning endpoint answers 503, and the connect wizard cannot connect a GitLab group |
You set LOGIN and the passwords one time, and Stategraph never changes them:
ALTER ROLE stategraph_mql LOGIN PASSWORD '<fdw-password>';
ALTER ROLE stategraph_provisioner LOGIN PASSWORD '<provisioner-password>';
Put the same values in STATEGRAPH_FDW_PASSWORD and STATEGRAPH_FDW_PROVISIONER_PASSWORD. When the connecting role does not have CREATEROLE, create the roles yourself before the first start, with LOGIN and a password. The Docker Compose initdb scripts create both roles with the development passwords stategraph_mql and stategraph_provisioner, so use these values in a development stack.
Managed PostgreSQL
create extension postgres_fdw, create server, and create user mapping need superuser rights. The rest of the migration does not: its only extension, pgcrypto, is trusted. The Docker Compose PostgreSQL runs the role as a superuser, so it needs no action. On Amazon RDS or Cloud SQL, the stategraph role is usually a database owner without superuser. To turn on Orchestration there, also do one of these:
- On Amazon RDS, grant the role
rds_superuser. - Create the foreign server and the user mapping in the
stategraphdatabase in advance.
Otherwise the migration fails, and the Stategraph server does not start. The error is in the server log.
Connect GitHub
Orchestration runs as a GitHub App that you own. Create it with the setup wizard or by hand, then give its credentials to Stategraph.
With the setup wizard
The setup wizard creates the App through the GitHub manifest flow, with the permissions and events below. It generates the private key and the webhook secret, and writes the values to a .env file. To create the App under an organization, set GH_ORG on the wizard container. For a GitHub Enterprise Server host, set GHE_HOST.
- Run the wizard:
docker run --rm -p 3000:3000 ghcr.io/stategraph/orchestration-setup:latest
- Open http://localhost:3000, follow the flow to GitHub, and create the App.
- Copy the values into the environment of your deployment:
GITHUB_APP_ID,GITHUB_APP_CLIENT_ID,GITHUB_APP_CLIENT_SECRET,GITHUB_APP_PEM,GITHUB_WEBHOOK_SECRET, andGITHUB_APP_URL. - In the App settings, set the webhook URL to
https://stategraph.example.com/api/github/v1/events. - Add the claim callback URL from Tenant self-claim.
GITHUB_APP_PEM can contain literal \n sequences, as the wizard writes it. Stategraph changes them to newlines.
By hand
Create a GitHub App in your organization with these permissions:
| Permission | Access |
|---|---|
| Actions | Read and write |
| Checks | Read |
| Contents | Read and write |
| Issues | Read and write |
| Metadata | Read |
| Pull requests | Read and write |
| Commit statuses | Read and write |
| Secrets | Read and write |
| Workflows | Read and write |
| Members (organization) | Read |
| Email addresses (account) | Read |
Subscribe it to the events issue_comment, issues, pull_request, pull_request_review, pull_request_review_comment, push, workflow_job, and workflow_run. Then:
- Set the webhook URL to
https://stategraph.example.com/api/github/v1/events. - Set a webhook secret, and put the same value in
GITHUB_WEBHOOK_SECRET. - Add
https://stategraph.example.com/api/v1/vcs-installations/github/claim/callbackas a callback URL. - Generate a private key, and put its contents in
GITHUB_APP_PEM. - Put the App ID in
GITHUB_APP_ID, and the client ID and secret inGITHUB_APP_CLIENT_IDandGITHUB_APP_CLIENT_SECRET. - Put the install URL,
https://github.com/apps/<your-app>, inGITHUB_APP_URL.
Tenant self-claim
With self-claim, tenant admins link their own installation from the console: they verify with GitHub, without an instance admin. Self-claim needs:
- Both
GITHUB_APP_CLIENT_IDandGITHUB_APP_CLIENT_SECRET. The server reads both or neither: when one is unset, the claim sends the browser back withgithub_claim=unavailable. - The callback URL
{STATEGRAPH_OAUTH_REDIRECT_BASE}/api/v1/vcs-installations/github/claim/callbackon the App. - The organization permission Members: read on the App.
GitHub Enterprise Server
Set GITHUB_API_BASE_URL and GITHUB_WEB_BASE_URL to your instance, for example https://github.example.com/api/v3 and https://github.example.com.
Connect GitLab
Orchestration connects to GitLab one group at a time. Set the GitLab variables with the setup wizard or by hand, then connect each group from the console. For each group, Stategraph creates a GitLab installation, stores its access token, and issues a webhook secret. Webhook verification, GitLab API calls, merge request comments, and pipeline starts for the group use these credentials.
GitLab version
Stategraph Orchestration requires GitLab 18.1 or later, Community or Enterprise Edition. gitlab.com always meets this. On self-managed GitLab, check the version with the metadata API:
curl --header "PRIVATE-TOKEN: <access-token>" "https://gitlab.example.com/api/v4/metadata"
Output:
{ "version": "18.1.0", "revision": "abc123def45", "enterprise": true }
The minimum comes from pipeline inputs. For each plan and apply, Orchestration creates a pipeline with inputs on POST /projects/:id/pipeline. The inputs carry the work token and the API base URL that the job calls back to.
- From GitLab 18.1, the parameter is generally available.
- From 17.10, it is behind the
ci_inputs_for_pipelinesfeature flag, which an administrator can turn off, so you cannot rely on 17.x. - Before 17.10, GitLab ignores it. The pipeline starts, but the job runs without its inputs and fails when it calls back.
The .gitlab-ci.yml must declare each input that Orchestration sends in its spec.inputs block, each with a default, as the pipeline file from the connect wizard does:
spec:
inputs:
TERRATEAM_TRIGGER:
type: string
default: "$TERRATEAM_TRIGGER"
WORK_TOKEN:
type: string
default: "$WORK_TOKEN"
API_BASE_URL:
type: string
default: "$API_BASE_URL"
RUNS_ON:
type: array
default: []
---
When an input is missing, GitLab rejects the pipeline, and the run fails to start with GITLAB_INPUTS_MISSING_DEFAULTS. The server log records Given inputs not defined in the `spec` section. The merge request gets an Unable to start workflow due to missing inputs comment that shows the required block.
GitLab variables
| Variable | Notes |
|---|---|
GITLAB_APP_ID |
Application ID of a GitLab OAuth application. A value that is not empty turns on GitLab in Orchestration |
GITLAB_APP_SECRET |
The secret of the application. Required when GITLAB_APP_ID is set |
GITLAB_ACCESS_TOKEN |
An access token with the api scope. Required when GITLAB_APP_ID is set |
GITLAB_API_BASE_URL / GITLAB_WEB_BASE_URL |
For self-managed GitLab: the root URL of the instance, for example https://gitlab.example.com, without /api/v4. Both default to https://gitlab.com |
STATEGRAPH_FDW_PROVISIONER_PASSWORD |
Password of the stategraph_provisioner role. Required to connect a group from the console. See The FDW bridge and its roles |
When GITLAB_APP_ID is set and GITLAB_APP_SECRET or GITLAB_ACCESS_TOKEN is missing, Orchestration does not start. It logs CONFIG : ERROR with a Key_error that names the variable.
Orchestration uses the OAuth application only for its own GitLab sign-in endpoints under /api/v1/gitlab, which the console does not call.
With the setup wizard
The setup wizard from Connect GitHub also has a GitLab flow. It collects the values and writes them out, but you create the GitLab user, token, and application yourself:
- Run the wizard, and open http://localhost:3000.
- When the wizard asks how GitLab reaches your server, enter the public host name, for example
stategraph.example.com. - Create a dedicated GitLab user for Stategraph, a bot account such as
acme-stategraph-bot. As that user, create a personal access token with theapiscope. Enter your GitLab instance URL and the token. - As the bot account, add an application in Preferences > Applications, named
Stategraph, with theapiscope and the redirect URI that the wizard shows,https://stategraph.example.com/api/v1/gitlab/callback. Enter its application ID and secret. - Copy the values into the environment of your deployment:
GITLAB_APP_ID,GITLAB_APP_SECRET,GITLAB_ACCESS_TOKEN,TERRAT_UI_BASE, andTERRAT_WEB_BASE_URL. The wizard does not writeGITLAB_API_BASE_URLorGITLAB_WEB_BASE_URL: for self-managed GitLab, add them yourself.
By hand
- Create a dedicated GitLab user for Stategraph. As that user, create a personal access token with the
apiscope, and put it inGITLAB_ACCESS_TOKEN. - As the same user, create an OAuth application with the redirect URI
https://stategraph.example.com/api/v1/gitlab/callbackand theapiscope. Put the application ID inGITLAB_APP_IDand the secret inGITLAB_APP_SECRET. - For self-managed GitLab, set
GITLAB_API_BASE_URLandGITLAB_WEB_BASE_URLto the root URL of the instance. - Give the
stategraph_provisionerrole a password, and put it inSTATEGRAPH_FDW_PROVISIONER_PASSWORD. See The FDW bridge and its roles.
Connect a group from the console
A tenant admin connects a GitLab group to the tenant from the Get Started page of the console. The connect wizard asks for the numeric group ID and an access token with the api scope.
- Personal namespaces are not supported.
- The wizard does not check the group ID. A wrong ID provisions an installation that never receives webhooks.
- Stategraph stores the access token write-only on the installation.
| Token | Notes |
|---|---|
| Group access token | Recommended: scoped to the group, not tied to a person. Needs GitLab Premium or Ultimate on gitlab.com, or any tier on self-managed GitLab |
| Project access token | For a single project |
| Personal access token | Any plan, but stops working if its owner loses access to the group. Use the classic form, to grant api |
Group and project tokens need at least the Developer role.
Then, in the GitLab project:
- In the webhook settings, add a webhook with the URL
https://stategraph.example.com/api/v1/gitlab/eventsand the secret token that the wizard shows. - Turn on Push events, Comments, Merge request events, and Job events, and save.
- Click Test > Push events on the webhook. The first delivery moves the installation from pending to installed.
- In Settings > CI/CD > Variables, set Minimum role to use pipeline variables to Developer, so that Orchestration can use pipeline variables.
- Add the
.gitlab-ci.ymlthat the wizard shows to the repository root, and push it to the default branch. If the project already has a.gitlab-ci.yml, merge the jobs into it. The file is also in Stategraph Cloud.
The GitLab webhook secret shows only once
Stategraph provisions the installation and returns its webhook secret only once. Copy it into GitLab before you leave the step. The only way to replace a lost secret is to rotate the credentials in Settings > Integrations, which issues a new secret and invalidates the old one.
Rotate the access token and the webhook secret in Settings > Integrations, or in the connect wizard when a group is connected. There, a tenant admin can also remove an installation from the tenant. Its plans and applies keep running. Only an instance admin can link it back, and the connect wizard cannot connect it again.
Self-managed GitLab
The instance must run GitLab 18.1 or later: see GitLab version. Set GITLAB_API_BASE_URL and GITLAB_WEB_BASE_URL to the root URL of the instance, for example https://gitlab.example.com. Orchestration reads both, but the console does not: enter the same root URL in the Group step of the connect wizard.
The .gitlab-ci.yml includes the template project terrateam-io/terrateam-template from the instance that runs the pipeline. On self-managed GitLab, first mirror gitlab.com/terrateam-io/terrateam-template to terrateam-io/terrateam-template on your instance, or pipeline creation fails.
Check names
The brand of a repository sets the prefix of the commit checks that Orchestration publishes, for example stategraph apply and stategraph plan: <dir> <workspace>:
| Repository | Prefix |
|---|---|
.stategraph/config, or a stategraph centralized repository |
stategraph |
.terrateam/config, or a terrateam centralized repository |
terrateam |
| Any other repository | TERRAT_BRAND, which is unset by default, so stategraph |
Moving the configuration file of a repository between .terrateam/ and .stategraph/ renames its checks, so branch protection rules that require the old names are not satisfied.
Verify
After the container restarts with the switch on, search its log:
docker compose logs server | grep -E 'terrat|GitLab|CONFIG : ERROR|GITHUB_WEBHOOK_SECRET|CREATE DATABASE'
- A correct start logs that Orchestration starts, and that the two roles are reconciled. With the GitLab variables set, it also logs
Starting GitLab Service. - A failed start logs the missing GitHub webhook secret, a
CONFIG : ERRORline for a missing GitLab variable, or theCREATE DATABASEstatement to run.
Then open the console, and set up your first repository from the Get Started page.
Next steps
- Pull request workflows: what Orchestration does on a pull request.
- Private runners: plans and applies on GitHub Actions or GitLab runners that you host.
- Environment variables: the Orchestration tuning settings.