carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1{{define "fleet_byo"}}{{template "top" .}}
2{{if .Flash}}<p class="banner">{{.Flash}}</p>{{end}}
3{{if .FlashErr}}<p class="banner error">{{.FlashErr}}</p>{{end}}
4<div class="pagehead">
5 <p class="eyebrow">Fleet · {{.Account.Name}}</p>
6 <h1>Bring your own fleet</h1>
7 <p class="lede">Run your apps on hardware you own. Your boxes dial out to the platform over an
8 outbound tunnel; nothing ever connects in.</p>
9</div>
10
11<h2>What you need</h2>
12<ul>
13 <li>A Linux box, arm64 or amd64, that can reach the internet outbound.</li>
14 <li>An object-storage bucket you own for the fleet's data.</li>
15 <li>Nothing listens inbound, ever — no port to open, no firewall rule to punch a hole in.</li>
16</ul>
17
18<h2>Create the fleet</h2>
19<p>From a terminal signed in to this console:</p>
20<pre><code>carlos fleets create -bucket &lt;your-bucket&gt; &lt;name&gt;
21carlos fleets add-box &lt;name&gt; &lt;label&gt;</code></pre>
22<p class="small">(shown once — the console does not store or re-show the plaintext)</p>
23<p class="small">ROTATION IS NOT REVOCATION: this refuses the box's NEXT dial-in with the OLD
24token, but does NOT evict a channel it already holds — a COMPROMISED box needs Detach, not
25another Rotate.</p>
26
27<h2>Configure the box</h2>
28<p>On the box, in a root-owned env file the agent's unit loads (see Hardening below):</p>
29<pre><code>AWS_SHARED_CREDENTIALS_FILE=/etc/carlos/credentials
30CARLOS_HIBERNATE_BUCKET=&lt;your-bucket&gt;
31CARLOS_FLEET=&lt;name&gt;
32CARLOS_FLEET_BOX=&lt;label&gt;
33CARLOS_REGION=fleet/&lt;name&gt;
34CARLOS_RELEASE_PUBKEY=&lt;this deployment's release public key&gt;
35CARLOS_TUNNEL_URL=&lt;this deployment's tunnel address&gt;
36CARLOS_TUNNEL_TOKEN=&lt;from carlos fleets add-box &lt;name&gt; &lt;label&gt; — shown once&gt;
37CARLOS_TUNNEL_NAME=&lt;name&gt;/&lt;label&gt;</code></pre>
38<p class="small">Release pointers are Ed25519-signed; without <code>CARLOS_RELEASE_PUBKEY</code> the
39box has no key to verify against and refuses to adopt anything.</p>
40<p class="small"><code>CARLOS_FLEET_BOX</code> is optional and defaults from
41<code>CARLOS_TUNNEL_NAME</code>'s label half. <code>AWS_SHARED_CREDENTIALS_FILE</code> points at
42an access key for the bucket above — <strong>your own bucket only</strong> — written root-owned
43mode 600 alongside the env file.</p>
44<p class="small">No <code>CARLOS_DEPLOYMENT_BUCKET</code>, and no AWS credentials for the
45platform's own bucket, belong in this file at all: the box reads its app releases and writes its
46heartbeats through the console's edge, over HTTPS, using the same tunnel token it already dials
47in with — not a bucket-level AWS grant.</p>
48<p class="small">Optional override: <code>CARLOS_STATUS_PREFIX=fleets/&lt;name&gt;/</code> — the box
49defaults to this on its own; set it explicitly only if you're pointing status/heartbeats
50somewhere non-standard.</p>
51
52<h3>Legacy direct-S3 (grandfathered)</h3>
53<p class="small">A box still running a pre-fleet-API agent build needs
54<code>CARLOS_DEPLOYMENT_BUCKET</code> set to the platform's own deployment bucket, plus an AWS
55credential granting it RW there, in addition to the hibernate-bucket vars above. New boxes should
56not be configured this way — it is kept working for boxes that haven't upgraded yet, not as a
57choice.</p>
58
59<h2>Hardening</h2>
60<ul>
61 <li>Run the agent as a dedicated non-root user, not root.</li>
62 <li>Make the env file root-owned, mode 600 — it holds the tunnel token.</li>
63 <li>Default-deny inbound on the box's firewall; it only ever dials out.</li>
64 <li>Turn on unattended security upgrades.</li>
65 <li>Keep the clock sane — chrony or systemd-timesyncd; leases compare timestamps.</li>
66 <li>Run the agent under a systemd unit with <code>ProtectSystem=strict</code> and
67 <code>NoNewPrivileges=yes</code> — see <code>internal/host/units/</code> in the platform repo
68 for the reference shapes.</li>
69 <li>Rotate the token if it may have leaked. If the box itself may be compromised,
70 <strong>Detach</strong> it instead — rotating only refuses its next dial-in, it does not evict
71 a channel the box already holds.</li>
72</ul>
73
74<h2>Then</h2>
75<p>The box appears on your fleet page within minutes — it self-reports on a heartbeat cadence, not
76instantly. From there, place an app on it with <code>carlos apps place</code> or from the app's
77Instances tab.</p>
78{{template "bottom" .}}{{end}}
79