🤖 Storage, logs, restarting and sleeping
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+230
−0
src/docs/logs.md+67 −0src/docs/sleeping.md+86 −0src/docs/storage.md+77 −0
diff --git a/src/docs/logs.md b/src/docs/logs.md| new file mode 100644 |
| index 0000000..928a804 |
| --- /dev/null |
| +++ b/src/docs/logs.md |
| @@ -0,0 +1,67 @@ |
| +# Logs |
| + |
| +`carlos logs` is one timeline rather than two. Your app's own stdout and |
| +stderr come back interleaved with the platform's account of what happened to |
| +it — which instance woke and on what version, when something restarted it, |
| +and when a wake failed, with the reason. |
| + |
| +```sh |
| +carlos logs -app hello -since 1h |
| +``` |
| + |
| +Lines print oldest to newest: a UTC timestamp, the stream, the instance, the |
| +message, and any structured fields your app attached, dimmed at the end. |
| + |
| +## Following |
| + |
| +`-f` leaves the command running and polls for new lines every two seconds. |
| + |
| +```sh |
| +carlos logs -app hello -f |
| +``` |
| + |
| +It runs close to live, not exactly live. Boxes ship what they have collected |
| +every ten seconds or so, so a follow arrives in small batches instead of |
| +scrolling a character at a time. Start one before a deploy and you will see |
| +the wake and the first requests. |
| + |
| +## Cutting it down |
| + |
| +An hour of a busy app is more than anyone reads. `-since` takes a duration |
| +back from now (`15m`, `24h`) or an RFC3339 timestamp, and `-until` closes the |
| +far end of the window. `-grep` matches an RE2 pattern against the message. |
| +`-host` narrows to a single instance when the app runs more than one. `-n` |
| +caps how many lines come back — a thousand by default, five thousand at most |
| +— and when the window has more than that, it tells you it is showing the |
| +newest. |
| + |
| +There is also `-stream`. `-stream app` drops the platform's commentary and |
| +leaves your own output; `-stream platform` does the opposite. |
| + |
| +## The half you can't get any other way |
| + |
| +Your app's output you could always have arranged for yourself. The platform |
| +stream is the part that needs a view from outside the process: `instance woke |
| +(version a1b2c3)`, `instance restarting (carlos restart)`, `instance |
| +stopping`, and — when a request got a 503 you cannot reproduce — a wake |
| +failure written in terms you can act on, like a socket left behind by a |
| +previous run, or nothing promoted on the channel this instance follows. |
| + |
| +A static site has no process and so has no app stream at all. Its platform |
| +events still show up, and they are the reason to run `carlos logs` against a |
| +site. |
| + |
| +## How far back it goes |
| + |
| +Seven days, and a size ceiling per app that drops the oldest hour first if |
| +an app is unusually chatty. If you need a longer record than that, have the |
| +app ship its own copy somewhere; the platform's retention is for debugging |
| +this week, not for keeping an audit trail. |
| + |
| +An empty result is worth a second look rather than a bug report. Platform |
| +events only happen when something happens — a wake, a restart, a failure — |
| +so a quiet hour of a quiet app really does have nothing in it. Widen to |
| +`-since 24h` before you conclude that logging is broken. |
| + |
| +The remaining flags, including `-level` and the repeatable `-field`, are in |
| +[carlos logs](https://carlosframework.com/docs/cli/#carlos-logs). |
diff --git a/src/docs/sleeping.md b/src/docs/sleeping.md| new file mode 100644 |
| index 0000000..127aa51 |
| --- /dev/null |
| +++ b/src/docs/sleeping.md |
| @@ -0,0 +1,86 @@ |
| +# Restarting and sleeping |
| + |
| +Your app's process gets cycled for reasons that have nothing to do with new |
| +code. You ask for it, a config change forces it, or the platform puts a quiet |
| +app to sleep and starts it again when somebody turns up. This page covers the |
| +first and the last. |
| + |
| +```sh |
| +carlos restart -app hello |
| +``` |
| + |
| +The reply says *restart requested*, which is the accurate word for what |
| +happened: the console wrote one small object, and every box serving the app |
| +notices it within seconds and cycles its own routes. No version moves, no |
| +channel is consulted, nothing is pushed at a box. |
| + |
| +What cycling looks like depends on how the instance runs. A long-running one |
| +stops and comes straight back. An on-demand one stops within seconds and |
| +starts again on the next request that needs it — for a quiet app, that may |
| +be a while. An instance that is already asleep is left asleep and |
| +starts fresh whenever it next wakes. |
| + |
| +Reach for restart when a process is wedged and you have no change to make. A |
| +deploy already cycles the app, and so does a config change — see |
| +[Deploying](/docs/deploying/) and [Env and secrets](/docs/config/) — so |
| +neither of those needs a restart after it. |
| + |
| +## What hibernation is |
| + |
| +An app nobody has talked to for a while is checkpointed to its bucket and its |
| +process stopped. The idle window belongs to the deployment; fifteen minutes |
| +is the default. The app's state was living in the bucket the whole time, so |
| +there is nothing to lose in the transition — the process was only ever the |
| +part that reads it. |
| + |
| +```sh |
| +carlos instances list -app hello |
| +``` |
| + |
| +The health column reads `asleep` for those instances and `running` for the |
| +rest. For anything without steady traffic, `asleep` is the ordinary |
| +condition, not a fault to clear. |
| + |
| +## What a visitor sees |
| + |
| +A request for a sleeping app is held at the edge while the instance wakes, |
| +then handed to it. The visitor waits longer for that page and then gets it; |
| +there is no error page and nothing for them to retry. Requests that arrive |
| +together during a wake all join the same one instead of each starting their |
| +own. |
| + |
| +The exception is a wake that takes an unusual length of time. Past about |
| +thirty seconds the request is answered with a 503 and a `Retry-After` rather |
| +than held open forever, and a wake that fails outright ends the same way. In |
| +both cases the reason is one line in the platform stream: |
| + |
| +```sh |
| +carlos logs -app hello -stream platform -since 15m |
| +``` |
| + |
| +## Traffic that doesn't wake anything |
| + |
| +The edge keeps its own cache of responses and checks it before it goes |
| +anywhere near the wake path, so a crawler asking for the same public page all |
| +afternoon can be answered without your app running at all. What makes a |
| +response eligible is your app saying so: a 2xx GET with `Cache-Control: |
| +max-age=…` and no `Set-Cookie`. Whatever number you set, the edge caps the |
| +entry at five minutes, so this cannot strand a page for long. Setting that |
| +header on your public pages is the single most useful thing you can do for an |
| +app that hibernates. |
| + |
| +A TLS handshake by itself never wakes anything either — only a request that |
| +would reach your app does. |
| + |
| +## Work that still happens |
| + |
| +A timetable set with [carlos |
| +schedule](https://carlosframework.com/docs/cli/#carlos-schedule) survives |
| +hibernation. Before an instance goes to sleep the platform records when its |
| +next job falls due, and wakes it when that time arrives; the job runs, and the |
| +usual idle window puts it back. You do not have to keep the app awake to keep |
| +its schedule. |
| + |
| +Flags for [carlos restart](https://carlosframework.com/docs/cli/#carlos-restart) |
| +and [carlos instances](https://carlosframework.com/docs/cli/#carlos-instances) |
| +are in the CLI reference. |
diff --git a/src/docs/storage.md b/src/docs/storage.md| new file mode 100644 |
| index 0000000..295fe12 |
| --- /dev/null |
| +++ b/src/docs/storage.md |
| @@ -0,0 +1,77 @@ |
| +# Object storage |
| + |
| +Uploads, generated files, anything your app wants to still have next week — |
| +none of it belongs on the box's local disk, because the process that wrote |
| +it there is not guaranteed to be the one serving your next request. Each app |
| +can have a bucket of its own instead, and the credentials for it arrive in |
| +your process as environment variables. |
| + |
| +```sh |
| +carlos store create -app hello |
| +``` |
| + |
| +That records the request and nothing more. No bucket exists yet: creating |
| +declares that the app wants one, and provisioning is a step on the Carloku |
| +side. `carlos store status` is where you watch for it landing. |
| + |
| +## What your app ends up with |
| + |
| +Five variables under a prefix, `CARLOS_STORE` unless you name another: |
| +`CARLOS_STORE_BUCKET`, `_REGION`, `_ACCESS_KEY` and `_SECRET_KEY`, plus |
| +`_ENDPOINT` where the backend has one. Your app reads them like any other |
| +config and talks to the bucket directly, with whichever S3 client its |
| +language already has. Nothing proxies that traffic. |
| + |
| +The bucket, region and endpoint are ordinary values — `carlos env list` will |
| +show them to you. The two keys are delivered through the sealed store, so |
| +they reach your process and stop being readable to anyone, including you. |
| +[Env and secrets](/docs/config/) explains why that is one-way. |
| + |
| +If your app already expects a particular set of names, say so at create |
| +time and skip the adapter code: |
| + |
| +```sh |
| +carlos store create -app hello -env-prefix BLOBS |
| +``` |
| + |
| +## Watching it arrive |
| + |
| +```sh |
| +carlos store status -app hello |
| +``` |
| + |
| +You get what you declared, what was granted, and what was provisioned — |
| +bucket, region, the user the credential belongs to, and the key id currently |
| +in use. Two of its lines are worth knowing before you meet them. One says a |
| +delivered variable has not reached your app's config yet. That is the |
| +ordinary state for a few seconds after any change, and a problem only if it |
| +persists. The other says a rotation was started and never finished. |
| + |
| +## Rotating the key |
| + |
| +```sh |
| +carlos store rotate -app hello |
| +``` |
| + |
| +A fresh credential is minted and delivered, and the previous one keeps |
| +working. Your app picks the new values up the way it picks up any config |
| +change: the box rewrites the environment for the app's routes and cycles the |
| +process, within seconds. Leaving the old key alive is the point of doing it |
| +in two halves — an upload that started under it gets to finish, and a second |
| +instance that has not converged yet keeps serving. |
| + |
| +Close the window when you are satisfied nothing is still using it: |
| + |
| +```sh |
| +carlos store rotate -finish -app hello |
| +``` |
| + |
| +Until you do, `status` keeps telling you a rotation is pending. |
| + |
| +One habit worth forming. The secret never appears at your terminal — rotate |
| +prints the new key's id and that is the whole of what you see — so if the |
| +value ever turns up somewhere it shouldn't, it came out of the app. Whatever |
| +your app dumps at startup is the first place to look. |
| + |
| +[carlos store](https://carlosframework.com/docs/cli/#carlos-store) has the |
| +sub-verbs and the rest of their flags. |