Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions .claude/skills/create-launch-post/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pnpm render-tile-images -- \
```

4. When Rivet itself is one of the tiles, pass `src/images/rivet-logos/icon-white.svg` and add `--ink-tile <n>` (1-based, left to right) for that position. That tile is painted as the product-mark badge — ink fill, `34.375%` radius, white ring and R filling the tile — instead of a black badge floating inside a white app tile. Never place a Rivet or product wordmark in a light tile. The badge is sized level with the smallest neighboring tile. Add `--no-wordmark` to drop the Rivet wordmark above the title when the title or a tile already carries the Rivet mark; the title and tiles shift up to stay balanced.
5. For a diagram hero instead of tiles, start from `scripts/render-byoc-hero.ts` (`pnpm render-byoc-hero -- --output-dir <path>`): a colored, simplified architecture diagram on paper using pine, sage, ink, one accent arrow, and brand-colored provider marks. Copy and adapt it per launch rather than adding flags.
5. Inspect both PNGs. If a title wraps to a third line or a tile crowds the title, shorten the title rather than hand-editing the output. Tile geometry lives in `TILE_LAYOUTS` in the renderer; change it there if a lockup genuinely needs different placement.

When short code demonstrations would help launch distribution, create a temporary JSON file outside the repository with one to four sections:
Expand Down
145 changes: 145 additions & 0 deletions .claude/skills/create-launch-post/scripts/render-byoc-hero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "playwright";

// Rivet BYOC hero: a colored, simplified hero-scale take on the docs'
// ByocArchitectureDiagram. Writes image.png (2048x1024) and social.png
// (2048x1238) plus scene.html.
//
// pnpm render-byoc-hero -- --output-dir /tmp/byoc-hero
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(SCRIPT_DIR, "../../../..");
const CARD_W = 2048;
const CARD_H = 1024;

const INK = "#1B1916";
const INK_SOFT = "#56524A";
const PAPER = "#EFEFEF";
const CREAM = "#F4F1E7";
const PINE = "#2E4034";
const SAGE = "#93A286";
const ACCENT = "#CB5A33";
const AWS_ORANGE = "#FF9900";
const GCP_BLUE = "#4285F4";

const strip = (s: string) => s.replace(/<\?xml[^>]*\?>/i, "").replace(/<!DOCTYPE[^>]*>/i, "");
const ensureViewBox = (svg: string) => {
const open = svg.match(/<svg\b[^>]*>/i)?.[0];
if (!open || /\bviewBox=/i.test(open)) return svg;
const w = open.match(/\bwidth="([\d.]+)(px)?"/i)?.[1];
const h = open.match(/\bheight="([\d.]+)(px)?"/i)?.[1];
return w && h ? svg.replace(open, open.replace(/<svg\b/i, `<svg viewBox="0 0 ${w} ${h}"`)) : svg;
};
const pathData = (svg: string) => svg.match(/\sd="([^"]+)"/)![1];

// The registry AWS mark is one path: four subpaths for "aws" then two for the
// smile. Split them so the smile can take the brand orange.
function awsMark(svg: string): string {
const subs = pathData(svg).split("z").filter((s) => s.trim()).map((s) => s + "z");
const letters = subs.slice(0, 4).join("");
// Absolute starts of the two smile subpaths, resolved from the relative moves.
const smile =
"M578.59 368.94" + subs[4].replace(/^m[\d.\- ]+/, "") +
"M607.78 335.65" + subs[5].replace(/^m[\d.\- ]+/, "");
return `<svg viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"><path d="${letters}" fill="${INK}"/><path d="${smile}" fill="${AWS_ORANGE}"/></svg>`;
}

async function buildHtml(): Promise<string> {
const [font, badgeRaw, awsRaw, gcpRaw] = await Promise.all([
readFile(path.join(REPO, "public/fonts/manrope/Manrope-Variable-latin.woff2")),
readFile(path.join(REPO, "src/images/rivet-logos/icon-white.svg"), "utf8"),
readFile(path.join(REPO, "public/images/registry/deploy-aws-ecs.svg"), "utf8"),
readFile(path.join(REPO, "public/images/registry/deploy-gcp-cloud-run.svg"), "utf8"),
]);
const badge = ensureViewBox(strip(badgeRaw)).replace(/#f0f0f0\b/gi, "#FFFFFF").replace(/#0f0f0f\b/gi, INK);
const aws = awsMark(strip(awsRaw));
const gcp = strip(gcpRaw).replace(/fill="#1b1916"/i, `fill="${GCP_BLUE}"`);

// Diagram geometry in card pixels.
const num = (x: number, y: number, n: number, stroke = PINE) =>
`<circle cx="${x}" cy="${y}" r="22" fill="${PAPER}" stroke="${stroke}" stroke-width="2.5"/>
<text x="${x}" y="${y + 9}" text-anchor="middle" font-size="26" font-weight="600" fill="${stroke}">${n}</text>`;

return `<!doctype html><html><head><meta charset="utf-8"><style>
@font-face { font-family: "Manrope"; src: url("data:font/woff2;base64,${font.toString("base64")}") format("woff2"); font-weight: 200 800; }
* { box-sizing: border-box; }
html, body { margin: 0; background: ${PAPER}; }
.stage { position: relative; width: ${CARD_W}px; height: ${CARD_H}px; overflow: hidden; background: ${PAPER}; }
.card { position: absolute; left: 0; top: 0; width: ${CARD_W}px; height: ${CARD_H}px; font-family: "Manrope", sans-serif; color: ${INK}; }
h1 { position: absolute; top: 88px; left: 0; right: 0; margin: 0; text-align: center; font-size: 96px; line-height: 1.06; letter-spacing: -0.015em; font-weight: 500; }
svg.diagram { position: absolute; left: 0; top: 0; width: ${CARD_W}px; height: ${CARD_H}px; font-family: "Manrope", sans-serif; }
.logo { position: absolute; display: flex; align-items: center; justify-content: center; }
.logo svg { display: block; height: 100%; width: auto; }
</style></head><body><div class="stage" id="stage"><div class="card" id="card">
<h1>Introducing Rivet BYOC</h1>
<svg class="diagram" viewBox="0 0 ${CARD_W} ${CARD_H}" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="ah-pine" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0L10 5L0 10z" fill="${PINE}"/></marker>
<marker id="ah-accent" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0L10 5L0 10z" fill="${ACCENT}"/></marker>
</defs>
<g fill="${INK}">
<!-- Your VPC -->
<rect x="944" y="300" width="900" height="560" rx="30" fill="${SAGE}" fill-opacity="0.18" stroke="${PINE}" stroke-width="3" stroke-dasharray="16 12"/>
<text x="992" y="366" font-size="32" font-weight="500" fill="${PINE}">Your VPC</text>

<!-- Rivet Cloud -->
<rect x="204" y="490" width="380" height="180" rx="22" fill="${INK}"/>
<text x="394" y="642" text-anchor="middle" font-size="38" font-weight="600" fill="#FFFFFF">Rivet Cloud</text>

<!-- Outbound only -->
<path d="M944 580 H590" fill="none" stroke="${ACCENT}" stroke-width="4" marker-end="url(#ah-accent)"/>
<text x="767" y="548" text-anchor="middle" font-size="26" font-weight="500" fill="${ACCENT}">Outbound only</text>
<text x="767" y="622" text-anchor="middle" font-size="24" fill="${INK_SOFT}">Updates and status</text>

<!-- Deployment inside the VPC -->
<rect x="1024" y="420" width="740" height="380" rx="22" fill="#FFFFFF" fill-opacity="0.7" stroke="${PINE}" stroke-width="2"/>
<rect x="1064" y="460" width="660" height="88" rx="16" fill="#FFFFFF" stroke="${INK}" stroke-width="2.5"/>
<text x="1394" y="516" text-anchor="middle" font-size="34" font-weight="600">Rivet operator</text>
<rect x="1064" y="572" width="660" height="88" rx="16" fill="${PINE}"/>
<text x="1394" y="628" text-anchor="middle" font-size="34" font-weight="600" fill="${CREAM}">Rivet control plane</text>
<rect x="1064" y="684" width="660" height="88" rx="16" fill="#FFFFFF" stroke="${INK}" stroke-width="2.5"/>
<text x="1394" y="740" text-anchor="middle" font-size="34" font-weight="600">FoundationDB</text>
<text x="1394" y="836" text-anchor="middle" font-size="24" fill="${INK_SOFT}">Managed by Rivet</text>
</g>
</svg>
<!-- Rivet badge on the Rivet Cloud card -->
<div class="logo" style="left:364px;top:522px;width:60px;height:60px">${badge}</div>
<!-- Provider marks, top-right of the VPC -->
<div class="logo" style="left:1636px;top:330px;height:48px">${aws}</div>
<div class="logo" style="left:1748px;top:330px;height:48px">${gcp}</div>
</div></div></body></html>`;
}

function parseOutputDir(argv: string[]): string {
const args = argv[0] === "--" ? argv.slice(1) : argv;
const index = args.indexOf("--output-dir");
const value = index >= 0 ? args[index + 1] : undefined;
if (!value) throw new Error("Usage: pnpm render-byoc-hero -- --output-dir <path>");
return path.resolve(value);
}

async function main() {
const OUT = parseOutputDir(process.argv.slice(2));
await mkdir(OUT, { recursive: true });
const html = await buildHtml();
await writeFile(path.join(OUT, "scene.html"), html);
const browser = await chromium.launch();
for (const target of [
{ name: "image", w: 2048, h: 1024 },
{ name: "social", w: 2048, h: 1238 },
]) {
const page = await browser.newPage({ viewport: { width: target.w, height: target.h }, deviceScaleFactor: 1 });
await page.setContent(html, { waitUntil: "load" });
await page.evaluate(() => document.fonts.ready);
await page.evaluate((h) => {
document.getElementById("stage")!.style.height = h + "px";
document.getElementById("card")!.style.top = Math.round((h - 1024) / 2) + "px";
}, target.h);
await page.screenshot({ path: path.join(OUT, `${target.name}.png`) });
await page.close();
console.log(`wrote ${target.name}.png`);
}
await browser.close();
}
main().catch((e) => { console.error(e); process.exitCode = 1; });
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"new-post": "tsx ./scripts/new-post.ts",
"render-launch-images": "tsx ./.claude/skills/create-launch-post/scripts/render-launch-images.ts",
"render-tile-images": "tsx ./.claude/skills/create-launch-post/scripts/render-tile-images.ts",
"render-byoc-hero": "tsx ./.claude/skills/create-launch-post/scripts/render-byoc-hero.ts",
"render-technical-image": "tsx ./.claude/skills/create-launch-post/scripts/render-technical-image.ts",
"gen:markdown": "tsx ./scripts/generate-markdown.ts",
"gen:skills": "tsx ./scripts/generate-skills.ts",
Expand Down
47 changes: 35 additions & 12 deletions src/content/posts/2026-09-15-introducing-rivet-byoc/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,59 @@
author: nicholas-kissel
published: "2026-09-15"
category: changelog
image: { file: "image.png?v=2" }
image: { file: "image.png?v=5" }
keywords: ["byoc", "bring your own cloud", "vpc", "aws", "google cloud", "terraform", "kubernetes", "enterprise", "self-host", "rivet actors"]
title: "Introducing Rivet BYOC"
description: "Run the Rivet control plane inside your own AWS or Google Cloud VPC, fully managed by Rivet. Your data stays in your account and Rivet Cloud never needs inbound access."
---

import ByocArchitectureDiagram from '@/components/docs/ByocArchitectureDiagram.astro';
import ByocRegionsDiagram from '@/components/docs/ByocRegionsDiagram.astro';

Today we're releasing **Rivet BYOC** (Bring Your Own Cloud): the Rivet control plane deployed inside your own AWS or Google Cloud VPC and managed by Rivet.

Until now you had two options. Rivet Cloud runs everything for you, or you self-host the control plane and own the provisioning, upgrades, and on-call that come with it. BYOC sits between them. Your data and compute stay in your account. Rivet handles deployment, updates, and maintenance.
- **Rivet Cloud**: Rivet runs everything.
- **Self-host**: you run everything.
- **BYOC**: your data and compute stay in your account. Rivet handles deployment, updates, and maintenance.

## How deployments work

<ByocArchitectureDiagram />

## Your VPC, operated by Rivet
A Rivet operator runs in your Kubernetes cluster and connects outbound to Rivet Cloud.

A Rivet operator runs inside your Kubernetes cluster and connects outbound to Rivet Cloud to receive deployment instructions. It applies updates to the control plane and FoundationDB locally and reports status back.
1. Request a deployment or update
2. The operator pulls instructions from Rivet Cloud
3. The operator applies the update inside your cluster
4. The operator reports status back to Rivet Cloud

- **No inbound access**: your VPC needs no inbound rules from Rivet Cloud. Your admin token stays in your cloud secret manager.
- **Public or private**: expose Rivet over HTTPS on your own hostname, or keep it reachable only from your private network.
- **Multi-region**: each region gets its own VPC, cluster, operator, and control plane. Regions share a container registry and talk over private connectivity.
- **Dashboard included**: inspect and manage your actors from the Rivet dashboard, tunneled to your deployment.
- **Public or private**: expose Rivet over HTTPS on your own hostname, or keep it on your private network.
- **Dashboard included**: manage your actors from the Rivet dashboard, tunneled to your deployment.

## Multi-region

<ByocRegionsDiagram />

- Each region gets its own VPC, cluster, operator, and control plane
- Data stays in the region that produced it. Pin EU users to an EU region for GDPR, or keep regulated workloads in-country.
- Regions talk over private connectivity and share a container registry
- Workers connect to their regional endpoint

## BYOC for your product too

If you build on Rivet, BYOC is also how you offer BYOC to your own customers. When a customer needs your product running inside their cloud, you create a BYOC project for them and they apply the setup kit in their AWS or Google Cloud account. Rivet brings up the control plane in their VPC and keeps it updated. You deploy your workers against it the same way you deploy against Rivet Cloud.
If you build on Rivet, BYOC is also how you offer BYOC to your own customers.

- **Same build everywhere**: the code you ship to your hosted offering is the code you ship into a customer's VPC.
- **Isolated per customer**: each deployment has its own VPC, cluster, storage, and admin token.
- **Nothing extra to operate**: Rivet handles upgrades and maintenance in every customer environment, not you.
- Create a BYOC project per customer. They apply the setup kit in their AWS or Google Cloud account.
- Rivet brings up the control plane in their VPC and keeps it updated.
- Deploy your workers against it the same way you deploy against Rivet Cloud. Same build everywhere.
- Every deployment is isolated: its own VPC, cluster, storage, and admin token.

## Availability

BYOC is available today on AWS and Google Cloud as part of [Rivet Enterprise Edition](/enterprise/), with a free 14-day trial. Create a BYOC project in the [Rivet dashboard](https://dashboard.rivet.dev) and follow the [quickstart](/cloud/byoc/quickstart). The deployment shuts down automatically when the trial ends. For air-gapped deployments, [talk to an engineer](/talk-to-an-engineer/).
- Available today on AWS and Google Cloud with [Rivet Enterprise Edition](/enterprise/)
- Free 14-day trial: create a BYOC project in the [Rivet dashboard](https://dashboard.rivet.dev) and follow the [quickstart](/cloud/byoc/quickstart). The deployment shuts down automatically when the trial ends.
- Air-gapped deployment? [Talk to an engineer](/talk-to-an-engineer/)

## Links

Expand Down
Loading