carlos / carloku Public

🤖 Storage, logs, restarting and sleeping

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 9309861974f1d69cae486f1ae51e431e216c0f5d parent 126e920
3 files changed, +230 −0
  • src/docs/logs.md +67 −0
  • src/docs/sleeping.md +86 −0
  • src/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.