carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1<!doctype html>
2<html lang="en">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>The CARLOS Platform</title>
7<meta name="description" content="πŸ€– The place where CARLOS apps run: ship an immutable release, get a canary URL in seconds, promote the same artifact to stable. Apps sleep to a bucket when idle and wake on demand. The whole platform is one binary, a bucket, and DNS β€” open, and trivially self-hostable.">
8<meta property="og:title" content="The CARLOS Platform">
9<meta property="og:description" content="A black-box deployment platform for CARLOS apps. One binary, a bucket, and DNS β€” Heroku's ease, keeping CARLOS's promises.">
10<meta property="og:type" content="website">
11<meta property="og:url" content="https://platform.carlosframework.com/">
12<style>
13 :root {
14 --bg: #ffffff;
15 --ink: #1d2029;
16 --muted: #64707f;
17 --accent: #0f5e85;
18 --accent-soft: rgba(15, 94, 133, 0.09);
19 --rule: #e7e9f0;
20 --max: 42rem;
21 --wide: 62rem;
22 }
23 @media (prefers-color-scheme: dark) {
24 :root {
25 --bg: #14151c;
26 --ink: #eef0f6;
27 --muted: #93a4ba;
28 --accent: #5db8dd;
29 --accent-soft: rgba(93, 184, 221, 0.12);
30 --rule: #2a2e38;
31 }
32 }
33 :root[data-theme="dark"] {
34 --bg: #14151c;
35 --ink: #eef0f6;
36 --muted: #93a4ba;
37 --accent: #5db8dd;
38 --accent-soft: rgba(93, 184, 221, 0.12);
39 --rule: #2a2e38;
40 }
41 :root[data-theme="light"] {
42 --bg: #ffffff;
43 --ink: #1d2029;
44 --muted: #64707f;
45 --accent: #0f5e85;
46 --accent-soft: rgba(15, 94, 133, 0.09);
47 --rule: #e7e9f0;
48 }
49 * { margin: 0; padding: 0; box-sizing: border-box; }
50 body {
51 background: var(--bg);
52 color: var(--ink);
53 font: 400 1.0625rem/1.65 Charter, Georgia, "Times New Roman", serif;
54 -webkit-font-smoothing: antialiased;
55 padding: 0 1.25rem;
56 }
57 a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
58 strong { font-weight: 700; }
59 code {
60 font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
61 font-size: 0.88em; background: var(--accent-soft);
62 padding: 0.1em 0.35em; border-radius: 0.25rem;
63 }
64
65 /* hero */
66 header {
67 max-width: var(--wide); margin: 0 auto;
68 padding: 5rem 0 3rem; text-align: center;
69 }
70 h1 {
71 font-size: 2.9rem; line-height: 1.1; letter-spacing: 0.01em;
72 margin: 0 0 0.85rem; font-weight: 700;
73 }
74 .tagline { font-size: 1.2rem; color: var(--muted); font-style: italic; }
75 .lede {
76 font-size: 1.2rem; max-width: 38rem;
77 margin: 1.5rem auto 0;
78 }
79 .cta {
80 margin: 2rem 0 0; display: flex; gap: 0.75rem;
81 justify-content: center; flex-wrap: wrap;
82 }
83 .btn {
84 font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
85 font-size: 0.95rem; font-weight: 600; text-decoration: none;
86 padding: 0.65rem 1.25rem; border-radius: 0.5rem;
87 }
88 .btn.primary { background: var(--accent); color: var(--bg); }
89 .btn.ghost { color: var(--accent); border: 1px solid var(--rule); }
90 .btn.ghost:hover, .btn.primary:hover { filter: brightness(1.1); }
91 .robot-note {
92 margin: 2.25rem auto 0; padding: 0.6rem 0.9rem; max-width: 36rem;
93 background: var(--accent-soft); border-radius: 0.5rem;
94 font-size: 0.9rem; color: var(--muted);
95 font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
96 }
97 .robot-note a { color: var(--accent); }
98
99 /* stat strip */
100 .stats {
101 max-width: var(--wide); margin: 0 auto;
102 border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
103 display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
104 text-align: center;
105 }
106 .stats div { padding: 1.4rem 0.5rem; }
107 .stats .n { display: block; font-size: 2rem; font-weight: 700; color: var(--accent); line-height: 1.1; }
108 .stats .l {
109 font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
110 font-size: 0.85rem; color: var(--muted);
111 }
112
113 /* sections */
114 main { max-width: var(--wide); margin: 0 auto; padding: 0 0 3rem; }
115 section { padding: 3rem 0; }
116 section + section { border-top: 1px solid var(--rule); }
117 section > * { max-width: var(--max); margin-left: auto; margin-right: auto; }
118 section > .cards { max-width: var(--wide); }
119 h2 {
120 font-size: 1.6rem; line-height: 1.25; font-weight: 700;
121 letter-spacing: -0.01em; margin-bottom: 0.4rem; text-align: center;
122 }
123 h2 a { color: inherit; text-decoration: none; }
124 h2 a:hover { color: var(--accent); }
125 .epigraph {
126 font-size: 1.15rem; font-style: italic; color: var(--muted);
127 margin-bottom: 1.4rem; text-align: center;
128 }
129 p { margin: 0 0 1.1rem; }
130 section > p:last-child { margin-bottom: 0; }
131
132 /* cards */
133 .cards {
134 display: grid; gap: 1rem; margin-top: 1.75rem;
135 grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
136 }
137 .card {
138 border: 1px solid var(--rule); border-radius: 0.75rem;
139 padding: 1.25rem 1.35rem;
140 }
141 .card.feature { border-color: var(--accent); background: var(--accent-soft); }
142 .card h3 { font-size: 1.1rem; font-weight: 700; margin: 0 0 0.35rem; }
143 .card .kicker {
144 display: block; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
145 font-size: 0.78rem; font-weight: 600; letter-spacing: 0.06em;
146 text-transform: uppercase; color: var(--accent); margin-bottom: 0.3rem;
147 }
148 .card p { color: var(--muted); font-size: 0.97rem; margin: 0; }
149
150 .keyline {
151 margin: 1.5rem auto; padding: 1rem 1.1rem;
152 border-left: 3px solid var(--accent);
153 background: var(--accent-soft);
154 border-radius: 0 0.4rem 0.4rem 0;
155 }
156 .keyline p:last-child { margin-bottom: 0; }
157
158 ul.plain { list-style: none; margin: 1.1rem auto; }
159 ul.plain li {
160 padding: 0.45rem 0 0.45rem 1.25rem; position: relative;
161 }
162 ul.plain li::before {
163 content: "β€”"; position: absolute; left: 0; color: var(--accent);
164 }
165
166 footer {
167 max-width: var(--wide); margin: 0 auto;
168 border-top: 1px solid var(--rule);
169 padding: 2rem 0 4rem; text-align: center;
170 color: var(--muted); font-size: 0.95rem;
171 }
172 footer p { margin-bottom: 0.6rem; max-width: var(--max); margin-left: auto; margin-right: auto; }
173</style>
174</head>
175<body>
176
177<header>
178 <h1>The CARLOS Platform</h1>
179 <p class="tagline">Where CARLOS apps run.</p>
180 <p class="lede">A black-box deployment platform for <a href="https://carlosframework.com">CARLOS</a> apps: ship a release, and the platform places it on instances, routes to it, sleeps it when idle, and wakes it on demand. Heroku's ease, keeping CARLOS's promises β€” and the platform itself is one binary, a bucket, and DNS.</p>
181 <div class="cta">
182 <a class="btn primary" href="#ship">What shipping looks like</a>
183 <a class="btn ghost" href="#status">How young is it?</a>
184 </div>
185 <p class="robot-note">πŸ€– This page was written by an LLM, on the ideas, instruction, and editing of humans. AI-written text here is always marked, visibly, like this β€” that's <a href="https://11factor.org/#x">factor X</a>.</p>
186</header>
187
188<div class="stats">
189 <div><span class="n">1</span><span class="l">binary, playing every role</span></div>
190 <div><span class="n">0</span><span class="l">control-plane servers</span></div>
191 <div><span class="n">0</span><span class="l">rebuilds from canary to stable</span></div>
192 <div><span class="n">72h</span><span class="l">bake before stable adopts</span></div>
193</div>
194
195<main>
196
197<section id="what">
198 <p>The framework defines the shape of an app; the platform is the substrate it runs on β€” the way Heroku stood beside the twelve factors. An author ships <strong>releases</strong>: immutable, content-addressed artifacts. The platform places them on <strong>instances</strong>, routes to them through a mesh, hibernates them when idle, and wakes them on the request that needs them. Apps from many authors run side by side on the same fleet, sharing the same blind infrastructure.</p>
199
200 <div class="keyline">
201 <p>The whole self-hosting story is one sentence: <strong>a deployment is a bucket, N hosts running one binary, and DNS.</strong> Keeping it one sentence is a design constraint on everything else.</p>
202 </div>
203
204 <p>There is no control-plane server to stand up, and none to be locked out of. All durable platform state β€” apps, releases, channels, instance records, leases β€” lives in an object-store bucket; hosts converge by watching it, and a burned-down box is replaced by pointing a fresh one at the bucket. The platform makes about itself the promise its instances already make: <strong>the box is disposable, the bucket is not.</strong> It is entirely open and trivially self-hostable, and two of its first three deployments <em>are</em> self-hosted β€” so anything the flagship needs that a self-hoster can't have is a design error, caught immediately.</p>
205</section>
206
207<section id="ship">
208 <h2><a href="#ship">Ship, canary, promote</a></h2>
209 <p class="epigraph">The dev pipeline is the prod pipeline, exercised on every push.</p>
210
211 <p><code>carlos ship</code> uploads a release under its content hash. A <strong>channel</strong> is a per-app pointer to a release β€” <code>stable</code>, <code>beta</code>, and ephemeral <code>canary/*</code> channels β€” and advancing one is a single pointer write. Instances subscribe to a channel and roll onto new releases at their next natural boundary: wake from hibernation, or a drain-and-restart sweep. Rollback is the same operation backwards.</p>
212
213 <div class="cards">
214 <div class="card">
215 <h3>Every branch gets a canary URL</h3>
216 <p><code>carlos canary</code> from a worktree builds, ships, creates a channel, spawns one instance bound to sandbox glue, and returns its URL β€” in seconds, not minutes. A dev environment is not a separate system; it is a channel with one subscriber.</p>
217 </div>
218 <div class="card">
219 <h3>Promotion never rebuilds</h3>
220 <p><code>carlos promote</code> points a wider channel at the same hash: canary β†’ beta β†’ stable. Every environment running release <code>abc123</code> runs byte-identical code. What you tested is what ships, because it is the same bytes.</p>
221 </div>
222 <div class="card">
223 <h3>Rollback is a pointer move</h3>
224 <p>A bad release hits gradually β€” instances adopt on wake β€” and pointing the channel back at the previous hash stops the bleed. Additive-only migrations mean old data stays readable under new code and new data survives the trip back.</p>
225 </div>
226 <div class="card">
227 <h3>Static sites ride the same channels</h3>
228 <p>A static release is a content-addressed file tree the edge serves directly β€” no process, nothing to hibernate. Same canary URLs per branch, same promotion, same rollback. This page ships that way.</p>
229 </div>
230 </div>
231</section>
232
233<section id="run">
234 <h2><a href="#run">Sleeping is the default state</a></h2>
235 <p class="epigraph">Most software is used a few hours a week. The platform bills like it.</p>
236
237 <p>An idle instance hibernates, and its database <strong>parks</strong> to object storage: a parked app is a few objects in a bucket β€” no process, no memory, no host doing anything on its behalf. The next request wakes it, on whichever eligible host answers first. Availability is continuous even when execution isn't.</p>
238
239 <ul class="plain">
240 <li><strong>Per-instance databases, continuously replicated.</strong> Every instance owns its own database, and it streams to the bucket from the first byte. Losing a host costs a restart, not a history: its leases expire and the instances wake elsewhere from their last parked state.</li>
241 <li><strong>Scheduling is deliberately dumb.</strong> No bin-packing optimizer β€” hibernation makes placement mistakes cheap. A badly-placed instance parks and wakes somewhere better next time.</li>
242 <li><strong>Scale is a declaration, not a rewrite.</strong> Parked β†’ embedded SQLite β†’ replicated β†’ an attached server database (one per instance, inside the app's trust domain, never multi-tenant) β†’ a dedicated pool. An app moves along that curve by changing its manifest, not its architecture.</li>
243 </ul>
244</section>
245
246<section id="place">
247 <h2><a href="#place">Placement you can point at</a></h2>
248 <p class="epigraph">Residency is enforced where the instance wakes, not promised in a policy PDF.</p>
249
250 <div class="cards">
251 <div class="card">
252 <span class="kicker">Residency</span>
253 <h3>Choose where data lives</h3>
254 <p>An app β€” or a single instance, because one customer may demand EU while another doesn't care β€” declares <code>residency: eu</code>. A host outside the region will not claim the lease, so there is no code path where the instance runs elsewhere "just this once during a failover": no eligible host up means the wake fails loudly. Parked data, streams, and blobs rest in region-scoped buckets. Residency covers rest as well as execution.</p>
255 </div>
256 <div class="card">
257 <span class="kicker">Dedicated pools</span>
258 <h3>Your own hardware, same platform</h3>
259 <p>Apps that hold real personal or payment data β€” not everything is end-to-end encrypted β€” can reserve hosts: their instances, parked data, and wake path only ever touch that pool, optionally with its own bucket, while still riding the shared edge, channels, CLI, and services.</p>
260 </div>
261 </div>
262</section>
263
264<section id="services">
265 <h2><a href="#services">Shared services: blind or seeing, never both</a></h2>
266 <p class="epigraph">Nothing that sees may store.</p>
267
268 <p>Every shared service carries a declared class, visible in the manifest of every app that consumes it. <strong>Blind services</strong> may hold data, but only data they cannot read: the object store holds parked databases and blobs but no plaintext of E2EE apps; the vault holds sealed blobs whose keys live elsewhere; the push relay holds device tokens and timing but never notification content; the OAuth relay passes tokens through sealed to the requesting instance, never retaining them. What makes them blind is falsifiable the CARLOS way β€” publish their state and see what leaks.</p>
269
270 <p><strong>Seeing services</strong> process plaintext β€” a URL unfurler, a PDF renderer, outbound mail β€” and are therefore forbidden to hold it. Not by promise: the platform grants a seeing service no database binding, no bucket write access, no persistent volume. Statelessness is imposed by construction. One honest caveat the mail service carries with it: the underlying email provider retains delivery metadata β€” bounces, complaints, suppressions β€” scoped per tenant.</p>
271</section>
272
273<section id="supply">
274 <h2><a href="#supply">Supply-chain honesty</a></h2>
275 <p class="epigraph">The scary step in any pipeline is the pointer that says "this is stable now."</p>
276
277 <ul class="plain">
278 <li><strong>Channel pointers are signed.</strong> A host agent rolls instances onto a release only when the pointer advancing the channel carries a valid signature β€” a bucket write alone is not a deployment.</li>
279 <li><strong>Releases bake before stable adopts them.</strong> A release must sit on its channel for 72 hours before stable instances roll onto it β€” time for a poisoned artifact to be noticed while its blast radius is still the people who opted in.</li>
280 <li><strong>Rollback never bakes.</strong> Pointing back at a hash that already served traffic takes effect immediately. The waiting period protects against new code, and going backwards is never new code.</li>
281 </ul>
282
283 <p>Every release records its build attestation β€” commit and builder β€” under its hash, and every platform change ships through the platform itself: worktree, canary channel, a miniature canary deployment, a headless-browser gate that fails on any console error. The release machinery is tested by being the only way anything ships, including itself.</p>
284</section>
285
286<section id="where">
287 <h2><a href="#where">Three deployments, one platform</a></h2>
288 <p class="epigraph">The Open promise is proven from day one, because the first two production users are self-hosters.</p>
289
290 <div class="cards">
291 <div class="card">
292 <h3>Tito</h3>
293 <p>Ticketing for conferences and events, running its own self-hosted deployment on its own account. Self-hosted use-case number one, and the platform's first proving ground.</p>
294 </div>
295 <div class="card">
296 <h3>Eleven</h3>
297 <p>The encrypted messenger, on its own deployment β€” and the code quarry: the platform's edge, host agent, channels, and mergeable stores were extracted from Eleven's proven machinery.</p>
298 </div>
299 <div class="card feature">
300 <h3>The flagship cloud</h3>
301 <p>The shared multi-tenant deployment, operated by the CARLOS authors, hosting Keymail, Woodstar, Slopbox, and Kass β€” the first four customers. Same open platform, differing from the self-hosted two in variables, never in shape.</p>
302 </div>
303 </div>
304
305 <p style="margin-top:1.75rem">Apps live at <code>&lt;sqid&gt;.oncarlos.com</code> β€” an address that never encodes region or host, so relocation and placement stay invisible to it. Custom domains ride on top as aliases.</p>
306</section>
307
308<section id="status">
309 <h2><a href="#status">Where it actually stands</a></h2>
310 <p class="epigraph">Fewer claims, all of them checkable β€” including this one.</p>
311
312 <p>The platform is young. It was extracted from Eleven and titogo β€” systems that kept rebuilding the same deployment shape until it was obviously one thing β€” and its first production tenant, Kass, migrated onto it on 2026-08-01. Everything above describes the design being built and proven, not a mature product with years of scar tissue. If that honesty reads oddly on a homepage β€” good. It is the same trade the framework makes: the platform holds itself to the promises it hosts, and a promise you can't check yet is stated as exactly that.</p>
313</section>
314
315</main>
316
317<footer>
318 <p>πŸ€– Written by an LLM (Claude), on the ideas, instruction, and editing of humans. AI-written text here is always marked and always disclosed β€” see <a href="https://11factor.org/#x">factor X</a>.</p>
319 <p>The platform is the substrate for apps built on <a href="https://carlosframework.com">the CARLOS framework</a>, keeping the promises set out in <a href="https://11factor.org">The Eleven Factors</a> β€” the way Heroku stood beside the twelve.</p>
320 <p><a href="https://github.com/carlosframework/platform">Source for the platform β€” and this site β€” on GitHub</a>. Open, of course.</p>
321</footer>
322
323</body>
324</html>
325