Skip to content

Your app's logs and resource figures

Two commands answer “what is my app saying?” and “how is it doing?” from your terminal, wherever the app runs. You need no SSH command, no container name and no dashboard:

Terminal window
npx opscrew logs # the app's latest log lines
npx opscrew status # what runs, with its CPU, memory, disk and restarts

Both only read. Nothing is installed on your server to collect anything, and nothing is kept: each run reads what is there at that moment.

Terminal window
npx opscrew logs # the web process's last 200 lines
npx opscrew logs --follow # then the new lines as they come, until Ctrl+C
npx opscrew logs --service db --since 1h # the database's lines of the last hour
npx opscrew logs --tail 50 # only the last 50 lines

The lines come oldest first, newest last, each with its time in UTC. The first line says how far back they go.

Flag What it does
--follow After the lines read, shows new ones as they arrive, until you stop it with Ctrl+C
--service <name> Another part of the app: a worker, a cron job or a service by its name in opscrew/plan.yaml (such as db), cron for the cron jobs, or edge for the reverse proxy in front of the app. Without it, the web process (app)
--since <duration> Only the lines of the last 30s, 15m, 2h or 3d
--tail <lines> How many of the latest lines (200 without it); on Cloudflare’s hosting, requests
--failed The lines OpsCrew kept of the last copy that failed its checks in a deploy (see below); not with --follow or --since
--app <key>, --env <name> Another app or environment, as every command takes them

A name --service doesn’t know is refused, and the refusal lists the names the app has.

  • The cron jobs run in one container, so --service <job> and --service cron both show the lines of every cron job.
  • The edge on a server is the reverse proxy every OpsCrew app on that server shares, so its lines are of all of them. In a Portainer, edge exists only when the stack’s web mode is caddy: it is then the Caddy in the app’s own stack.

When a new version fails its health check, a worker or cron job doesn’t stay up after the switch, or the check from outside fails, OpsCrew removes that copy and the previous version keeps serving. Before it removes the copy, it keeps its last 500 lines on the server, in .failed-deploy/ in the app’s folder, readable by the deploy user only:

Terminal window
npx opscrew logs --failed # the part that failed
npx opscrew logs --failed --service worker # a worker's kept lines

The first line says which copy it was, what it failed and when, for example “Lines of the copy of sha256:ab12cd34ef56 that failed its health check at 2026-10-31 10:04 UTC; OpsCrew removed it.” The lines are masked as every line is. OpsCrew keeps only the last failed deploy’s lines, and a successful deploy leaves them, so check the time. When none are kept, logs --failed says so.

A Portainer, Cloudflare’s hosting and GitHub Pages keep none: OpsCrew has no shell in a Portainer, and a site’s deploy starts no copy that could fail a health check.

Where the app runs How logs reads
A server OpsCrew created docker logs over SSH, as the deploy user with the project’s deploy key, as status does
Your own server docker logs over SSH, through the login OpsCrew uses there
A Portainer Docker’s API through Portainer, with PORTAINER_API_KEY
Your computer Your local Docker
Cloudflare’s hosting, server-rendered Cloudflare’s logs of the Worker’s requests; --follow uses Cloudflare’s live tail
Cloudflare’s hosting with a static site, and GitHub Pages No request logs: the last deploy runs instead

On a server, logs runs fixed, read-only Docker commands (a listing of the part’s containers, and docker logs), built only from names it has checked. It can’t run anything else on the server, and each read is written to OpsCrew’s audit log without the server’s address. When you stop --follow, the command ends on the server within about a second.

In a Portainer, --follow asks for new lines every 2 seconds, because Portainer holds back a live stream for about 40 seconds before it sends anything. A standard user’s access token sees only that user’s own stacks: when the app’s containers aren’t visible to the token, logs says so (see The tokens and what they may do).

When the server doesn’t answer over SSH, logs says so and suggests npx opscrew verify.

Each deploy starts new containers, and each container keeps up to 30 MB of logs (three files of 10 MB). Lines from before the last deploy, and lines older than those 30 MB, are gone. The first line of the output gives the time of the oldest line shown.

For more history, Grafana Cloud keeps 14 days of your app’s lines on its free tier, and self-hosted monitoring keeps them on your own server for as long as you set.

Every line passes OpsCrew’s masking before it is shown. It masks:

  • the values OpsCrew generated for the app, such as the database password;
  • the values of the names in your plan’s app.env, from the file OPSCREW_APP_ENV_FILE names, when you set it;
  • anything shaped like a known kind of secret, such as an API token or a private key.

A masked value shows as [REDACTED:<kind>]. A secret of your own with no known shape is masked only when OpsCrew knows its value: set OPSCREW_APP_ENV_FILE to a file with the production values of the names in your plan’s app.env to cover it.

For a server-rendered app, logs shows each recent request of the Worker: its method and path (never the query string, which can hold secrets), its status and outcome, then the lines the app printed for it and any error it raised. OpsCrew shows no request headers and no visitor addresses.

  • --tail counts requests, not lines: each request comes whole, its line with the method, path, status and outcome followed by its console lines and errors. Without --tail, the last 200 requests; at most 2,000.
  • Past requests can take up to about 30 seconds to show. --follow shows new requests at once.
  • The live tail is shared: Cloudflare keeps one live tail per Worker, shared by everyone on the account, so stopping --follow also stops wrangler tail and the dashboard’s live logs of that Worker. Cloudflare samples a busy Worker’s live tail.
  • Requests for the site’s static files run no code, so they are in no log.
  • --service doesn’t apply: the Worker is the whole app.
  • The permission: reading past requests needs Account → Workers Observability → Read on CLOUDFLARE_API_TOKEN, on top of the Workers Scripts → Edit that hosting needs. The live tail works with Workers Scripts → Edit alone. npx opscrew init names a missing permission.

Cloudflare keeps the logs within its plan’s allowance. On Workers Free, Cloudflare keeps nothing past the allowance until the next day; on a paid plan, the extra is billed. Nothing in Cloudflare’s answers says the allowance was reached, so logs always states it. As OpsCrew read Cloudflare’s Workers pricing and Observability pricing on 2026-10-28:

Workers Free Workers Paid
Until 2026-11-30 200,000 log events a day, kept 3 days 20 million log events a month, kept 7 days
From 2026-12-01 0.5 GB of logs a day, kept 7 days; past it, nothing is kept until 00:00 UTC 50 GB a month included, then USD 0.25 per GB

GitHub Pages and static sites on Cloudflare

Section titled “GitHub Pages and static sites on Cloudflare”

These hosts serve the site’s files without running any of its code, so they keep no request logs. logs says so in one sentence, then shows the site’s last 10 deploy runs, each with its result and a link. --service and --follow are refused there, with the reason.

npx opscrew status shows what runs, and how it is doing right now:

  • The server: its load over 1, 5 and 15 minutes against its number of CPUs, its memory in use of the total, and its disk used and free;
  • Each container: its CPU, its memory against the limit OpsCrew gave it, how many times it restarted since the last deploy, and how long it has been running.

On a server, one fixed, read-only command reads all of them at once. It never reads the containers’ settings, so no environment value comes back.

Figures past a threshold are flagged with what they mean and what to do:

Flag When What to do
Warning A container uses 90% or more of its memory limit; at the limit, the system stops it and Docker starts it again Raise its memoryMb in opscrew/plan.yaml and run npx opscrew provision, or find what in the app holds on to memory
Warning, then high The disk is 85% full, and 95% for the higher severity (the same thresholds as secure) Remove old images (docker image prune as the deploy user), or give the server a bigger disk
Warning A container restarted since the last deploy: it crashed or ran out of memory npx opscrew logs --service <name> shows why
Where the app runs What status shows Named as not available
A server OpsCrew created, or your own Everything above
A Portainer Each container’s figures, and the host’s CPUs and total memory The host’s load, memory in use and disk: Docker’s API through Portainer doesn’t give them. Containers the token’s user can’t see are named too
Your computer Each container’s figures, and Docker’s CPUs and total memory Your computer’s load, memory in use and disk
Cloudflare’s hosting, server-rendered The last 24 hours’ requests, errors and CPU time per request, against Workers Free’s 100,000 requests a day and 10 ms of CPU time per request
Cloudflare’s hosting, static site The Worker’s versions and custom domains Request figures and request logs: Cloudflare serves the site’s files without running any code
GitHub Pages The latest build and its result Any figure: no code of the site runs

On Cloudflare:

  • Errors are the requests that raised an exception, not every answer with a 4xx or 5xx status.
  • Requests for the site’s static files run no code and aren’t counted.
  • The figures lag about a minute.

Deploys: what runs, and whether deploys are paused

Section titled “Deploys: what runs, and whether deploys are paused”

status also shows the version that runs, the one before it, and whether the environment’s automatic deploys are on:

Deploys (read from the server and GitHub now):
Running: 9e8d7c6 "Add invoices page" (sha256:12ab34cd56ef), since 2026-10-31 10:04 UTC
Before it: 3f2a1c9 "Fix checkout total" (sha256:ab12cd34ef56)
Deploys: on (each push to main deploys)

On a server and on your computer it reads the app’s .env and .env.previous, which the switch writes; in a Portainer, the images labelled opscrew-<env> and opscrew-<env>-previous in GitHub Container Registry. The commit, its message and the build time come from the image’s provenance; when OpsCrew can’t read them from GitHub, it shows the short digest and says why. A site shows its versions in its own section.

After a rollback the last line says since when the deploys are paused, which version opscrew rollback pinned, and how to resume. While the rollback’s run hasn’t finished, it also says that the environment runs another image than the pinned one. When GitHub can’t be read, status shows the last rollback this computer recorded instead.

When the scheduled check is on, status also reads its state from GitHub:

  • whether it runs;
  • when GitHub turned it off, and how to turn it on again;
  • the open alerts, each with its link.
  • History: status shows this moment only, and logs only what the containers still keep. For charts over days, use Beszel, Grafana Cloud or self-hosted monitoring.
  • Say what to change: opscrew advise reads the same figures, with 14 days of history when self-hosted monitoring is on, and proposes a larger or cheaper server, a higher memory limit or a fix for the disk, priced.
  • Search across days, or across apps: logs reads one app and environment at a time.
  • Request logs of a static site, on GitHub Pages or on Cloudflare’s hosting.
  • The host’s disk in a Portainer: neither Docker nor Portainer gives it.
  • Logs Cloudflare didn’t keep: on Workers Free, Cloudflare keeps nothing past the allowance until the next day.

The OpsCrew plugin reads the same logs and figures through read-only tools when you ask “why is my app failing?” or “how much memory does it use?”. It returns at most 500 lines, masked as in the terminal, and never follows. For more lines or --follow, run npx opscrew logs in your terminal. It can also read a failed deploy’s kept lines, and shows the version running, the one before it and whether deploys are paused; a rollback itself stays in your terminal, since it asks for your approval.