← all posts
// devops · langgraph

Deploying LangGraph: platform, container, or cron job

I've shipped the same LangGraph app three ways in eight months: once on the managed platform, once as a container I babysit, once as a cron job that runs and dies. All three were right at the time, which is annoying if you came here for a single answer. Here's the menu as I actually use it, plus the Postgres surprise that reset my defaults.

the managed platform

LangGraph Platform stands a graph up fast. You get a task queue so a long run doesn't block a request for ninety seconds, horizontal scale, scale-to-zero between bursts, and the Studio UI for poking at live threads. For a team with no ops muscle and a graph that genuinely runs long, that bundle is worth money. The cost is the usual two-part tariff: a bill that tracks usage, and lock-in to their runtime shape, so the day you want out you're rewriting your deploy story. I keep it in mind for spiky, long-running, high-concurrency work. My actual workloads mostly weren't that.

the container I babysit

Most of the time I wrap the compiled graph in a small FastAPI app and run it as one container against a Postgres checkpointer. The server is unremarkable, which is the point:

saver = AsyncPostgresSaver(pool)
app = graph.compile(checkpointer=saver)
events = app.astream_events(payload, config={"configurable": {"thread_id": tid}})

One endpoint streams events out, one Postgres URL, deploy it anywhere that runs a container. The client's ticket triage pipeline serves a few hundred threads a day and this handles it on one small instance with headroom left. No queue, no platform, no Studio, and at that load I've never once missed them. When I need to watch a thread I lean on tracing, which is a separate discipline from deploy.

the humble cron

Some graphs aren't services at all. Our release-notes generator and a couple of overnight jobs on the old 3090 box in my closet are batch graphs: they wake on a schedule, chew through a bounded pile of work, and exit. For those, a container plus a cron entry is the entire architecture. No always-on server, no scale-to-zero because zero is already the resting state. This is the same shape as running local agents overnight. Kick it off, let it run while nobody's watching, read the output over coffee. Reaching for the platform here would be paying a subscription to schedule a script.

The deploy question is really one question: does this graph need to be awake when nobody is asking it anything?

the Postgres sizing surprise

Here's the mistake that taught me the most. I assumed the checkpointer database would stay small (it's only workflow state, how big could it get). Then I looked, six weeks in, and the checkpoints table was 14 GB while the entire business database it fronted was under 400 MB. The checkpointer writes state on every node transition, and I was keeping the full message history in state, so a forty-step thread persisted the whole growing transcript forty times over. Write amplification I had never budgeted for. Two fixes: I stopped storing raw messages in durable state and kept only what the next node needed, and I added a nightly prune of checkpoints older than the resume window I actually rely on, which is about nine days. The table settled under 2 GB. It stayed there.

the two days I ran on memory

One more, because it's the kind of thing nobody admits out loud. My first container deploy worked perfectly in staging, then quietly ran the in-memory checkpointer in production for two days, because I'd wired Postgres into local config and forgotten it in the prod environment. Every redeploy wiped all in-flight threads. It surfaced when a refund waiting on human approval evaporated after a routine deploy and the approver swore up and down she'd never seen it. The moment you pick the Postgres checkpointer, you've signed up to operate a database that grows every time a node fires, and to actually confirm it's the one running. Test that resume survives a restart in the environment that matters, not the one on your desk.

Platform when it's spiky and long. A container when it's a normal service. Cron when it's a batch job wearing a graph costume. That's the whole menu, and eight months in I still reach for the middle one first.

#langgraph#deploy#devops