Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions docs/ephemeral-environments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Ephemeral environments with GraphOS

> Spin up a fully composed supergraph per PR (and tear it down on merge or close), without manual GraphOS plumbing. Tracks [AS-222](https://apollographql.atlassian.net/browse/AS-222).

Ephemeral envs let a reviewer hit a real Router + subgraph URL that reflects exactly the change in a PR. The Apollo-native primitive that makes this work is the [GraphOS variant](https://www.apollographql.com/docs/graphos/graphs#variants) β€” each variant has its own composed supergraph schema, its own Router config, and its own usage stats.

## Topology

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GraphOS β”‚
PR #142 push ──► β”‚ variant: pr-142 ◄──┐ β”‚
β”‚ variant: main β”‚ rover β”‚
β”‚ variant: prod β”‚ publish β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
CI β–Ό β”‚
──► spin up Router pointed at variant pr-142 β”‚
deploy subgraph services on pr-142.example β”‚
comment PR with the env URL β”‚
β”‚
PR merged / closed ──► tear down namespace ──► rover graph delete my-graph@pr-142
```

One variant per PR, one short-lived Router + subgraph deployment per PR, automatic cleanup on PR close.

## Step 1 β€” Variant lifecycle in CI

On `pull_request: [opened, synchronize, reopened]`:

```bash
# Create the variant by publishing one subgraph against it. The variant is
# implicitly created on first publish; no separate "create variant" call.
rover subgraph publish my-graph@pr-${PR_NUMBER} \
--name products \
--schema ./subgraphs/products/schema.graphql \
--routing-url https://pr-${PR_NUMBER}.products.example.com
```

On `pull_request: [closed]`:

```bash
# Delete the variant and all its subgraphs β€” wipes the composed supergraph,
# usage data, checks history, and any contracts. `rover graph delete` exits
# non-zero if the variant doesn't exist, so guard the step with `|| true`
# (or skip it if the variant was never created on this PR).
rover graph delete my-graph@pr-${PR_NUMBER} --confirm || true
```

The variant key (`pr-<number>`) is the contract between CI and infrastructure. Use the GitHub-provided `PR_NUMBER`, not a sha or branch name, so re-pushes still target the same variant.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rover variant delete is not a real command. Use rover graph delete my-graph@pr-${PR_NUMBER} --confirm (deletes the variant and all its subgraphs). Also, the comment above says this is idempotent/safe when the variant was never created, but rover graph delete exits non-zero on a missing variant and will fail the CI teardown step; guard it (e.g. || true) or remove the idempotency claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at 81f0912: Step 1 teardown now uses rover graph delete my-graph@pr-${PR_NUMBER} --confirm || true, and the surrounding comment correctly states the command exits non-zero on a missing variant and is guarded with || true rather than claiming idempotency. This thread can be resolved.

## Step 2 β€” Subgraph deployment

Same pattern as your prod deploy, parameterized by PR number. On Kubernetes, that's a per-PR namespace; on Fly.io / Render, it's per-PR apps named `pr-<n>-products`, `pr-<n>-reviews`, etc.

The only Apollo-specific config: each subgraph's `routing-url` published to GraphOS must resolve from the Router pod. For Kubernetes, that's the in-cluster service DNS; for Fly.io/Cloud Run, the public HTTPS URL.

## Step 3 β€” Router pointing at the variant

The Router fetches its supergraph schema and config from Apollo Uplink based on `APOLLO_GRAPH_REF`. Set it to the per-PR variant:

```yaml
env:
- name: APOLLO_GRAPH_REF
value: my-graph@pr-142
- name: APOLLO_KEY
valueFrom:
secretKeyRef: { name: apollo-key, key: APOLLO_KEY }
```

Router automatically refetches when the variant's supergraph schema changes (every re-push during the PR's life). No restart needed.

## Step 4 β€” PR comment with the URL

After the deploy completes, post a comment with the ephemeral URL. Use a deduped marker so re-pushes update the comment instead of creating new ones β€” the pattern in [`apollosolutions/proposal-pr-bot`](https://github.com/apollosolutions/proposal-pr-bot) works here.

## Permissions

Use a [GraphOS service account API key](https://www.apollographql.com/docs/graphos/api-keys/) scoped to one graph. The key needs:

- `graph admin` or `graph contributor` to publish subgraphs to new variants
- `graph admin` to delete variants

Store it as a GitHub Actions secret. Rotate quarterly.

## Cost / quota awareness

- **Variants are unlimited** on enterprise plans but each one counts as an active variant on usage-based plans. Tear them down promptly on PR close.
- **Usage reporting** from PR Routers is real usage data. Filter it out of dashboards by `graph.variant_id` to avoid polluting prod metrics.
- **Checks** against ephemeral variants don't carry forward to prod β€” the validity of a `pr-142` check expires when the variant is deleted.

## See also

- [Apollo Connectors and Router on each variant](https://www.apollographql.com/docs/graphos/graphs/federated-graphs)
- [Service accounts and API keys](https://www.apollographql.com/docs/graphos/api-keys/)
- [`apollosolutions/proposal-pr-bot`](https://github.com/apollosolutions/proposal-pr-bot) β€” companion tooling for keeping PR ↔ proposal in sync (AS-89)