Self-hosted Enterprise

Run the Enterprise build of Stategraph, ghcr.io/stategraph/stategraph-server, with Docker Compose, connect GitHub or GitLab, and get your first plan and apply on a pull request. For Kubernetes, ECS, and Cloud Run, see Self-hosting. For the free open-source edition, see Self-hosted Open Source.

The Enterprise build needs a license key to complete the first-time setup. Put it in .env as STATEGRAPH_LICENSE_KEY=<key> before the first start, or enter it on the License step of the setup screen. Contact sales for a key. See Editions.

Before you begin

  • Docker Engine 20.10 or later, and Docker Compose v2.
  • Port 8080 free on the host.
  • For Orchestration, a URL that GitHub or GitLab can reach over HTTPS.
  • For GitHub, admin rights in the GitHub organization, to create and install a GitHub App.
  • For GitLab, gitlab.com or self-managed GitLab 18.1 or later (see GitLab version), a group (personal namespaces are not supported), and the rights to create an access token, add a project webhook, and change the project's CI/CD settings.

Create the Compose project

  1. Create a directory.
  2. In it, create docker-compose.yml:
services:
  db:
    image: postgres:17-alpine
    user: postgres
    environment:
      POSTGRES_PASSWORD: "stategraph"
      POSTGRES_USER: "stategraph"
      POSTGRES_DB: "stategraph"
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "stategraph", "-U", "stategraph"]
      interval: 3s
      timeout: 3s
      retries: 5
    volumes:
      - db:/var/lib/postgresql/data/
    networks:
      - stategraph

  server:
    image: ghcr.io/stategraph/stategraph-server:latest
    env_file:
      - .env
    environment:
      DB_HOST: "db"
      DB_PORT: "5432"
      DB_USER: "stategraph"
      DB_PASS: "stategraph"
      DB_NAME: "stategraph"
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 120s
    depends_on:
      db:
        condition: service_healthy
    networks:
      - stategraph

networks:
  stategraph:

volumes:
  db:
  1. Create .env:
STATEGRAPH_UI_BASE=http://localhost:8080

STATEGRAPH_UI_BASE, the public URL of the console, is required. All other settings, including Orchestration, go into .env. Leave optional variables unset, not empty: for some, an empty value differs from an absent one.

Start and verify

  1. Start the services:
docker compose up -d
  1. Check the containers:
docker compose ps
  1. Check that Stategraph is ready:
curl -f http://localhost:8080/health/ready

At start, Stategraph migrates the stategraph database, then serves requests. /health/ready returns a non-200 status until the migrations finish. /health/live answers as soon as the web server is up. See Health checks.

First login

Open http://localhost:8080. The first visit shows the setup screen. It asks for the license key first, unless STATEGRAPH_LICENSE_KEY is set in .env. With local authentication, the default, it then creates the first account, with a password of at least 8 characters, and signs you in as the instance admin.

As on Stategraph Cloud, you can connect GitHub or GitLab, or import your state. To connect, you need Orchestration, which the next section turns on. For Google or OIDC sign-in, see Access control.

Enable Orchestration

1. Create the GitHub App or the GitLab application

Run the setup wizard one time, on any computer with a browser:

docker run --rm -p 3000:3000 ghcr.io/stategraph/orchestration-setup:latest

GitHub

  1. Open http://localhost:3000, and sign in to GitHub when asked.

The wizard creates the App with the permissions, webhook, and credentials that Orchestration needs. Then it shows the settings for .env and the steps to finish on GitHub. Keep that page open until you write .env.

  1. In the App settings on GitHub, set the webhook URL to <public-url>/api/github/v1/events, with the webhook secret that the wizard generated.
  2. Register <public-url>/api/v1/vcs-installations/github/claim/callback as a callback URL.
  3. Grant the App the organization permission Members: read. Tenant admins can then link an installation from the console.
  4. Install the App on the organization that holds your repositories.

<public-url> is the value of STATEGRAPH_UI_BASE and STATEGRAPH_OAUTH_REDIRECT_BASE below. To create the App by hand, see Enable Orchestration.

GitLab

The wizard creates nothing in GitLab. It tells you what to create, and collects the values.

  1. Open http://localhost:3000. When asked how GitLab reaches your server, enter the host name of your public URL.
  2. Create a dedicated GitLab user for Stategraph.
  3. As that user, create a personal access token with the api scope, and enter it with your GitLab instance URL.
  4. As the same user, in Preferences > Applications, add an application with the api scope and the redirect URI that the wizard shows, <public-url>/api/v1/gitlab/callback. Enter its application ID and secret.

The wizard then shows GITLAB_APP_ID, GITLAB_APP_SECRET, and GITLAB_ACCESS_TOKEN for .env. Each GitLab group gets its own access token and webhook secret later, when you connect it in the console.

On self-managed GitLab, see Self-managed GitLab for the two URL variables that the wizard does not write, and the CI template to mirror.

2. Create the Orchestration database and roles

Orchestration keeps its own terrateam database on the same PostgreSQL server. Stategraph reads it through postgres_fdw as the stategraph_mql role. Create both one time in the running db container, with a password that you choose:

docker compose exec db psql -U stategraph -d stategraph \
  -c "CREATE DATABASE terrateam OWNER stategraph;" \
  -c "CREATE ROLE stategraph_mql LOGIN PASSWORD '<fdw-password>';"

For GitLab, also create the provisioning role, which the console uses to connect a group, with its own password:

docker compose exec db psql -U stategraph -d stategraph \
  -c "CREATE ROLE stategraph_provisioner LOGIN PASSWORD '<provisioner-password>';"

Orchestration updates the grants of both roles after every migration. On managed PostgreSQL such as Amazon RDS, the postgres_fdw connection needs superuser rights (rds_superuser). See Enable Orchestration.

3. Write the variables to .env

STATEGRAPH_UI_BASE becomes the public URL, and the TERRAT_* values derive from it. Replace .env with:

STATEGRAPH_UI_BASE=https://stategraph.example.com
STATEGRAPH_OAUTH_REDIRECT_BASE=https://stategraph.example.com
STATEGRAPH_ORCHESTRATION_ENABLED=true
TERRAT_API_BASE=https://stategraph.example.com/api
TERRAT_UI_BASE=https://stategraph.example.com
TERRAT_WEB_BASE_URL=https://stategraph.example.com
STATEGRAPH_FDW_PASSWORD=<fdw-password>

For GitHub, add the settings from the wizard:

GITHUB_APP_ID=<app id>
GITHUB_APP_CLIENT_ID=<oauth client id>
GITHUB_APP_CLIENT_SECRET=<oauth client secret>
GITHUB_WEBHOOK_SECRET=<webhook secret>
GITHUB_APP_URL=https://github.com/apps/<your-app>
GITHUB_APP_PEM=<private key>

For GitLab, add the settings from the wizard and the password of the provisioning role:

GITLAB_APP_ID=<application id>
GITLAB_APP_SECRET=<application secret>
GITLAB_ACCESS_TOKEN=<access token>
STATEGRAPH_FDW_PROVISIONER_PASSWORD=<provisioner-password>
Variable Purpose
STATEGRAPH_OAUTH_REDIRECT_BASE Base of the OAuth callback URLs, including the GitHub claim callback. Default http://localhost:<port>, which GitHub cannot reach
STATEGRAPH_ORCHESTRATION_ENABLED Turns on Orchestration and the postgres_fdw connection at start
TERRAT_API_BASE The public URL plus /api, for the runner
TERRAT_UI_BASE, TERRAT_WEB_BASE_URL The public URL. With the same host as STATEGRAPH_UI_BASE, that host serves only the Stategraph console. For the Terrateam console too, give TERRAT_UI_BASE its own host: see Two consoles
STATEGRAPH_FDW_PASSWORD The stategraph_mql password. The server does not start without it
GITHUB_APP_ID, GITHUB_APP_PEM The App ID and private key, which the wizard writes on one line with \n for line breaks
GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET The App's OAuth client. Also verifies the GitHub identity of a tenant admin who links an installation
GITHUB_WEBHOOK_SECRET Required with GITHUB_APP_ID, or Orchestration does not start
GITHUB_APP_URL The App's install page, linked from the console
GITLAB_APP_ID, GITLAB_APP_SECRET, GITLAB_ACCESS_TOKEN Turn on GitLab in Orchestration. When GITLAB_APP_ID is set, the other two are required
STATEGRAPH_FDW_PROVISIONER_PASSWORD The stategraph_provisioner password. Without it, the console cannot connect a GitLab group

TERRAT_DB_* need no setting: Orchestration uses the DB_* values with the database name terrateam. Telemetry is on by default. To turn it off, set TERRAT_TELEMETRY_LEVEL=disabled. All variables are in Environment variables.

4. Restart

Restart, and follow the server log:

docker compose up -d
docker compose logs -f server

Orchestration migrates the terrateam database at start. If Orchestration does not start, the log gives the cause, usually a missing GitHub webhook secret. A missing GitLab variable logs a CONFIG : ERROR line that names it.

Your first pull request

From here, the flow is the same as on Stategraph Cloud, which has the GitHub and GitLab steps, the workflow and pipeline files, the example repository, and what to check when no plan starts. Runs execute on your GitHub Actions or GitLab CI runners. The server only dispatches them.

GitHub

  1. In the console, open Get Started > Set up your first repository > Connect GitHub, and claim your installation:
    • An instance admin sees every unclaimed installation.
    • A tenant admin who is not an instance admin verifies with GitHub first, and sees only installations on organizations that they administer.
  2. Pick a repository that the App can access, or fork the example repository.
  3. Add .github/workflows/terrateam.yml to the default branch.
  4. Open a pull request that changes a .tf file. Your GitHub App posts the plan comment.
  5. Comment stategraph apply, then merge.

GitLab

  1. In the console, open Get Started > Set up your first repository > Connect GitLab. This needs admin rights on the tenant.
  2. Enter the GitLab URL and group, the repository, and an access token with the api scope. In GitLab, add the project webhook to <public-url>/api/v1/gitlab/events with the secret that the wizard shows one time, and set the minimum role to use pipeline variables to Developer.
  3. Add the .gitlab-ci.yml that the wizard shows to the default branch.
  4. Create a branch from the default branch, change a .tf file, and open a merge request. The owner of the group's access token posts the plan comment.
  5. Comment stategraph apply, then merge.

Next steps