Your Container Logs Are Ephemeral Twice Over — Plan Around It
Container logs here capture everything your app writes to stdout and stderr, viewable as a live stream or a tail of recent output. What catches people is that they vanish in two different ways: they rotate once they reach roughly 10 MB, and they're gone entirely when the container is recreated — which happens on every redeploy. So the log you want during an incident is often the one that just aged out, and the log from before your fix is gone the moment you ship the fix. Plan for that and logs become a debugging tool; assume permanence and they'll disappoint you exactly when it matters.
What gets captured
Both standard streams. Anything your process writes to stdout or stderr shows up — print(), console.log, your logging library's default handler, framework request logs, and crash tracebacks.
What doesn't show up is anything your app writes to a file. A logging config that rotates into /app/logs/app.log produces a beautiful log nobody can see from the panel, in a directory that's discarded on the next redeploy unless it's on a volume. If you want it in the log stream, write it to stdout.
Two language-specific gotchas worth knowing, because both produce logs that look mysteriously empty:
- Python buffers stdout when it isn't attached to a terminal, so output can sit in a buffer and die with the process.
PYTHONUNBUFFERED=1fixes it and is set for you on the zip path; set it yourself in a hand-written Dockerfile. - Node is generally fine, but a process that exits immediately can lose buffered writes on some stream types. If you log-then-
process.exit(), you may lose the log.
The first way they disappear: rotation
Logs are kept as a small number of capped files — roughly 10 MB in total before the oldest is dropped.
For normal operation that's a lot of history. For a misbehaving app it's very little: a crash loop restarting every five seconds, printing a traceback and a few startup lines each time, can churn through the whole budget quickly. The result is thousands of identical recent failures and no sign of the first one.
That inverts the usual debugging instinct. Scroll to the oldest entry you still have, not the newest — later iterations of a loop often fail differently (a port still held, a lock file left behind) and chasing the newest error leads somewhere else entirely.
If you need time to think, stop the app. That freezes what's left instead of letting the loop keep eating it.
The second way: redeploy
This one surprises people more. A redeploy doesn't restart your process inside the same container — it replaces the container. The old container's logs belong to the old container, and they go with it.
So the sequence people hit is: something breaks, you spot the error, you push a fix, and now you can't compare against the previous behaviour because that history no longer exists.
The habit that fixes it costs nothing: copy the relevant log out before you deploy the fix. Select it, paste it into the issue or a scratch file, then ship. Half the value of a log is the comparison, and you can only make it if you kept a copy.
Note the distinction from a crash-restart, which reuses the same container and keeps its log stream. Both get called "a restart," which is why this feels inconsistent until you know the difference.
Make the logs you do have worth reading
With a modest budget, what you log matters more than how much:
- Log once at startup, before anything can fail — version, environment, which config it loaded. That single line turns "it exits immediately" into "it exits after config load," which is most of the diagnosis.
- Log the identifier you'll search for. A user ID, a job ID, a delivery ID. Grepping a log for "error" is useless; grepping for a specific ID is how you actually trace something.
- Log failures with context, not just the exception. "Failed" tells you nothing; "failed fetching feed https://… after 3 retries" tells you where to look.
- Don't log every request on a small quota. Successful, unremarkable traffic is exactly what pushes the interesting entries out of retention.
- Log successes for rare things. A nightly backup that silently stopped running is indistinguishable from one that's working unless it leaves a line behind.
Don't log secrets
Anything in the log stream is readable by anyone with access to the app in the panel, and it'll sit there until it rotates.
The usual accidents are debug dumps of a whole config object, an exception handler that prints the request including an Authorization header, and a database URL logged at startup with the password in it. Log the name of a variable, not its value, when the value is a credential — and if something sensitive did get logged, rotate it rather than waiting for the log to roll.
If you need history, ship it
The platform keeps a rolling window, not an archive. That's fine for debugging and not enough for anything you need to look back on.
Two options, and one caveat:
- An external log service. Send structured logs to whatever you already use. This is the right answer if you genuinely need retention, search, or alerting.
- Write to a volume for a small, self-managed archive. Bounded rotation is essential here: log files that grow forever eat your disk quota, and total disk overruns stop every app in the account. Also remember a file on a volume isn't in the log stream — you'd be maintaining two things.
For most small projects the honest answer is neither: keep the rolling window, copy out anything interesting before you redeploy, and spend the effort on logging the right things instead.
FAQ
How long are container logs kept?
Until they rotate — roughly 10 MB of recent output — or until the container is recreated, whichever comes first.
Do my logs survive a redeploy?
No. A redeploy replaces the container, and its logs go with it. A crash-restart within the same container keeps them.
Why are my Python logs empty?
Python buffers stdout when not attached to a terminal, so output can be lost when the process dies. Set PYTHONUNBUFFERED=1.
Are logs written to a file visible in the panel?
No. Only stdout and stderr are captured. A file log needs a volume to persist and still won't appear in the log stream.
How do I keep logs long term?
Ship them to an external logging service, or write bounded, rotated files to a volume — watching the disk quota, since overruns stop every app in the account.
Why can't I find the original error in a crash loop?
Repeated restarts fill the rolling window and push the first failure out. Check early, scroll to the oldest entry you still have, and stop the app to freeze what's left.
Wrapping up
Treat the log panel as a window onto what your app is doing right now, not a record of what it did. Two habits cover almost everything that goes wrong with that: log one useful line at startup and one identifier per operation, and copy anything interesting out before you push the fix that erases it. Neither costs anything, and both are much easier than reconstructing an incident from a log that rotated away while you were reading it.