Snapshot and check
Run your app once on your own machine, let Cohostly write down what it needs, test it with check, and deploy it from the .cohostly folder.
Some apps are not a clean template. They read settings, need a special package, or start with an unusual command. Instead of describing all that by hand, you run the app once and let Cohostly watch. Think of a removal firm that photographs every room before it packs: nothing is guessed, and the photos say what goes where.
cohostly snapshot -- npm start cohostly check cohostly deploy
Three commands, three jobs:
snapshot.cohostly/ folder.checkdeploy.cohostly/ folder. See deploy from the snapshot.What you need
- macOS or Linux, Intel or Arm. On Windows both commands stop with
Capture runs on macOS and Linux. On Windows, use WSL2. - No Docker for
snapshot. Docker forcheck. - Your own toolchain.
snapshotruns your project's real build in a scratch copy. If your app needs Node or Python to build, they must be installed. - Railpack, the open-source build planner. The CLI downloads it once, the first time it is needed, and checks it against a fixed checksum. Messages:
Downloading Railpack 0.43.0 (once) ...,Could not download Railpack (<status>). Check your connection and try again.,The downloaded Railpack did not match its checksum. Nothing was run. - To be signed in, so the values can be sealed (see secrets in a snapshot).
snapshot
cohostly snapshot [--expires <Nd>] [--account <id>] [--root <dir>] [--timeout <duration>] [--probe-path <path>] [--compare-status] [--forward-loopback] -- <start command ...>
Only options before the -- belong to the CLI. Everything after it is your start command.
--root <dir>path--timeout <duration>time, such as 60s or 2m30s.--probe-path <path>text/.--compare-statuson or offcheck compare only the status and content type, for pages whose body changes on every visit. Default off.--forward-loopbackon or off127.0.0.1. Adds a small helper (socat) to the image. Default off.--expires <Nd>daysenv.enc stays valid. 1d to 30d. Default 30d.--account <id>textRun it the right way
- Use your production start command, the one you would run for real:
npm start,python app.py,gunicorn app:app. Not a dev server with hot reload, which records the wrong command and port. - The command runs directly, with no shell. For
cd,&&orVAR=value, say so:
cohostly snapshot -- sh -c "cd web && npm start" cohostly snapshot -- env PORT=3000 npm start
- Sign in first, so the values are sealed.
What it does
- Starts your command and waits up to
--timeoutfor it to listen on a port. - Probes
--probe-pathand records the answer. - Reads the environment values your app was started with.
- Asks the build planner (Railpack) for a plan.
- Runs your project's own build in a scratch copy, for up to 20 minutes, to find build-time variables (see values the build needs).
- Chooses a machine size.
- Writes the
.cohostly/folder.
The watching step takes seconds. An app with a build step can take much longer. All progress goes to your terminal's error stream.
Files it writes
.cohostly/DockerfileTODO(cohostly):, WARNING(cohostly): or NOTE(cohostly):..cohostly/cohostly.jsonruntime.language (set to dockerfile), start command, health.port, health.path, optional compute and the names of the secrets. A compute you edited by hand is kept on the next snapshot..cohostly/capture.jsoncheck adds a verification record to it..cohostly/env.json0600). Replaced by env.enc after sealing..cohostly/.gitignoreenv.json, so the plain values never reach git..cohostly/env.enc0600), safe to commit.The Dockerfile is an ordinary Dockerfile, and cohostly.json is the same settings file the platform always reads.
What it prints
snapshot (.cohostly/capture.json) runtime: node on node:22-slim [observed] start: npm start [observed] ports: 3000 env: API_KEY, STRIPE_KEY (values in .cohostly/env.json, owner-only) no value: STRIPE_KEY Wrote .cohostly/ (Dockerfile, cohostly.json, capture.json). Next: cohostly check, then cohostly deploy. Sealed 1 value(s) into .cohostly/env.enc (only your account can deploy them; expires 2026-11-26). The file is safe to commit.
The exact wording of the first lines can differ. The last two lines are the important ones.
Notes in the Dockerfile
Only three kinds of note exist.
TODO(cohostly) is a question the tool could not answer. cohostly deploy stops while one is open.
- The app never listened on a port while it was observed. Which port does it serve on? Run snapshot again with a longer
--timeout, or fix the port incohostly.json. - No start command was recorded. What command starts the app?
- The original app answered HTTP 500 or more at the probe path, and that answer is what
checkcompares against. If that failure is not expected, run snapshot again with--probe-pathset to a page that works.
WARNING(cohostly) never blocks. The one it gives: the app accepts connections on 127.0.0.1 only. Listen on 0.0.0.0 (or read HOST), or use --forward-loopback.
NOTE(cohostly) is information: the machine size it chose, the build-time variables it found, and variables that had no value.
Secrets in a snapshot
Values come from the environment your app was started with, or from a .env file. They are never written into the Dockerfile, cohostly.json, a command line or any output. Names are written, values are not.
- Signed in: the values are sealed into
.cohostly/env.encand the plainenv.jsonis deleted.Sealed N value(s) into .cohostly/env.enc (only your account can deploy them; expires YYYY-MM-DD). The file is safe to commit. - Not signed in, or offline: sealing fails. The plain
env.jsonstays:Could not seal the values: <why>and.cohostly/env.json now holds them in plain text (git-ignored, but readable on this machine). Fix the above and run cohostly snapshot again to seal them, then delete env.json.
A deploy carries at most 100 secrets. env.enc belongs to one account and expires. After that, or for another account, run snapshot again.
Values the build needs
Some frameworks copy a value into the files the build produces, for example a public address inside a browser bundle. That value must exist while the app is built, not only when it starts. If it does not, the built app acts as if it were unset. A login button that never appears is the usual sign.
Snapshot finds these without knowing any framework. It builds a scratch copy once, gives every variable your app reads a unique marker value, and searches the files the build wrote for the markers. Variables it finds get a bare ARG NAME line before the build step. On deploy the platform turns each ARG NAME into ENV NAME="value" from the sealed values, for those names only. If you write your own Dockerfile, add ARG NAME before the step that needs it. Only public values belong here: anything copied into browser code is visible to every visitor. Changing one means deploying again.
check
cohostly check [--generated] [--root <dir>] [--wait <duration>]
check builds the Dockerfile that would be deployed, starts it, and compares it with what snapshot saw. Think of a test drive before you buy. It needs Docker.
--generatedon or off.cohostly/Dockerfile even if you have your own ./Dockerfile. Default off.--root <dir>path--wait <duration>time, such as 2m90s.It uses your own ./Dockerfile if you have one, otherwise .cohostly/Dockerfile.
- Success:
ok: built and answered like the original app (42s). - Failure:
the <phase> step failed (step: <step>):followed by the last 25 log lines with secret values removed, then what it observed. That can be a module the app could not import, or system packages that provide files a command looked for. If nothing explains the failure it sayscannot decide: nothing observed explains this failure; read the log above.Use that evidence. Do not guess a package name. - For your own Dockerfile it says it was not changed, and offers
cohostly check --generatedandcohostly deploy --generated. For the generated one it says to copy.cohostly/Dockerfileto./Dockerfile, edit that, and run check again.
check rewrites .cohostly/capture.json. After every run, pass or fail, it adds a verification record (verified yes or no, the time, the image and a hash of the source). It never edits the Dockerfile.
Without Docker it stops with a message that Docker is needed to build the image, and adds that the snapshot is still deployable: the platform builds it. So you can skip check.
Exit code: the capture tool's. Zero on success, non-zero on failure. Output goes to the error stream.
Deploy from the snapshot
cohostly deploy [folder] [--project <name>] [--generated] [--force] [--verbose]
Chosen when the folder has .cohostly/cohostly.json and you gave no .cohostly file and no --manifest. Put the folder first if you give one. See the warning on put an app online.
[folder]path.cohostly/ in it. Default ..--project <name>textname in .cohostly/cohostly.json, which is the folder name.--generatedon or off--forceon or offTODO(cohostly) questions are still open.--verbose, -von or offA snapshot deploy never asks a question. It ignores --set, --yes, --draft, --manifest and --no-env-file.
It prints Deploying <project> with your Dockerfile ... when the root has its own Dockerfile, or ... with the generated Dockerfile .... Then the outcome as on put an app online, with the settings always shown.
What is uploaded: the app folder without .cohostly, node_modules, .git, .next, .venv, venv, __pycache__, and without root .env, .env.local, .env.production and .env.production.local. The generated Dockerfile is added unless the root has its own. The settings go as they are (with name replaced by the project). Values go only as secrets, from env.json or env.enc.
Stops and refusals:
The snapshot has N open question(s) that were not decided (TODO lines in .cohostly/Dockerfile): ... Settle them, or deploy anyway with --force.--force. Exit 1.There is no snapshot in <dir>. Make one with: cohostly snapshot -- <start command>snapshot first..cohostly/cohostly.json has no health.port. The app never listened while it was observed; set the port there, or snapshot again with a longer --timeout.Current behaviour: the last two refusals appear as Fatal: Error: ... followed by a stack trace, not as a tidy message. The first line is the one to read.
WARNING(cohostly) lines print as Warning: ... and NOTE(cohostly) lines as Note: .... They never stop the deploy. A snapshot deploy that is held for approval exits 1.
Changing the build
- Take over the Dockerfile. Copy
.cohostly/Dockerfileto./Dockerfilein the project root and edit that. A root Dockerfile is used as it is and must be single-stage. If it fails, its own error is shown and it is left alone.check --generatedanddeploy --generateduse the generated one instead. - Change settings (port, health path, bindings) in
.cohostly/cohostly.json. - Change the machine size. Snapshot writes
computeonly when the default (512 MB and 1 vCPU) is not enough, for example 2048 MB fornext build. Editcomputein.cohostly/cohostly.json. A size you set by hand is kept when you snapshot again. A build killed partway through an install or build is usually out of memory, so raise the size one step.
Files read and written
Writes .cohostly/* (snapshot) and .cohostly/capture.json (check). Reads .cohostly/* (check, deploy). Caches Railpack under ~/.cohostly/tools/ and the public key under ~/.cohostly/keys/. Needs a saved key or PLATFORM_API_KEY for sealing and for deploy. COHOSTLY_CAPTURE and COHOSTLY_RAILPACK can point at your own copies of the tools.
Exit codes
01TODO(cohostly) without --force, or a deploy held for approval.othersnapshot and check return the capture tool's own exit status.Examples
Snapshot a Node app, give the build a longer wait, then test it:
cohostly snapshot --timeout 90s -- npm start cohostly check --wait 3m
Snapshot an app whose home page changes on every visit, using a health page instead:
cohostly snapshot --probe-path /health --compare-status -- gunicorn app:app
Snapshot an app in a sub-folder, then deploy it under a name:
cohostly snapshot --root web -- npm start cohostly deploy . --project shop-web
A full worked story is in put your app online.