Vol. 26 · Application Engineering
Deployment & DevOps for an LLM Service
This volume is not about the code inside the agent, but about the operation outside: ingress, TLS, forwarded headers, “root path”, probes, secrets, queues, tracing, metrics, token/cost budgets, autoscaling, rollout/rollback and the place of “MCP” in the production circuit. This is a map of what it takes for an LLM service to not just run, but live and experience real traffic.
Ops Frame
01Ops FrameLLM service in production = code + network + state + operation
When the service goes out, its reliability is determined not only by the graph code. There are edge proxies, TLS, readiness, steady state, queues, timeouts, token budget, tracing and rollback strategy. The real system becomes multi-layered.
textTHE FULL CARTINE
client
↓
edge / ingress / TLS
↓
FastAPI app
↓
LangGraph runtime
↓
external state / queues / tools / models
↓
logs / metrics / traces / alerts02Ops FrameProduction begins where the service has a life cycle.
In dev, you just run uvicorn. In production, you need to roll out, heat, check, scale, limit, observe and be able to roll back. This is the main difference between operation and local launch.
production cycle
- deploy
- serve
- observe
- rollback
03Ops FrameNot only uptime, but also token economics are especially important for LLM services.
A conventional API often has pain in the CPU or DB. The LLM service adds to this tokens, latency external model providers, the cost of reranker/tool loops and the length of the spoken state. The ops model is closely related to budgets and guardrails.
textrequest rate
× average tokens
× model latency
× tool count
× retry policy
= real operational price of the service04Ops FrameLLM service is more convenient to design as stateless compute + external state
That's a key operational idea. If the web-process is almost stateless, and threads, checkpoints, queues and artifacts are brought out, then the service is easier to scale, transfer between nodes and experience restarts. For LLM runtime, this is especially important because of the ‘thread id’ and durable execution.
textDesirable model
replica A ─┐
replica B ─┼──→ shared persistence / queue / object store
replica C ─┘
Then any instance can continue the same conversation thread.05Ops FrameIn an LLM service, “alive” and “ready” rarely mean the same thing.
The process may be alive, but not ready to receive traffic: graph/service objects are not yet available, persistence is not available, configuration is not warmed up, client adapters are not raised. Therefore, “healthz” and “readyz” should almost always be separated.
That'll come in handy.
- liveness
- readiness
- startup probe
Edge, Ingress and HTTPS
06EdgeIngress/reverse proxy is an external login to a service, not just another YAML.
Kubernetes Ingress and similar edge layers solve several tasks at once: set HTTP/HTTPS routes outward, route traffic to the internal Service, know TLS termination, name-based routing and load balancing. This is a separate, very important operational layer before the application.
textEDGE PATH
internet
↓
ingress / load balancer
↓
service
↓
pod / app07EdgeTLS is usually terminated at the edge, and the application already comes HTTP.
FastAPI docs separately remind: HTTPS and certificates live below the HTTP layer. In practice, this often means that TLS ends on proxy/ingress, and before Uvicorn, the application receives internal HTTP traffic. Therefore, certificates, renewals, and SNI are usually served not by an application, but by an edge component.
practical conclusion
- TLS certs more often on proxy
- More often than not, FastAPI app
- The app needs to understand the original scheme/host.
08EdgeForwarded headers and 'root path' break URLs if not configured
Behind-proxy is not just about beauty. If a proxy puts HTTPS and path prefix, and the application doesn’t trust forwarded headers or doesn’t know about “root path”, it starts generating incorrect redirect URLs and documentation along the wrong paths.
pythonproxy adds:
X-Forwarded-For
X-Forwarded-Proto
X-Forwarded-Host
app must trust them:
--forwarded-allow-ips
if proxy strips/adds prefix:
configure root_path09EdgeFor SSE and long responses edge timeouts more important than it seems
LLM services often stream tokens, tool progress, and updates. This means that ingress, proxy and client timeouts must be aligned with streaming pattern. Otherwise, the service “works”, but edge cuts off the connection before the user sees the answer.
routine
- proxy idle timeout less than generation time
- Disconnect client without correct cancellation
- Long-lived HTTP stream support
10EdgeCORS, auth and rate limits are often more logical to put closer to the edge.
Some policies can be done in the application, but operationally more convenient when at least part of the protection and normalization of traffic is at the input: origin policy, TLS, IP filtering, basic rate limiting, size limiting. Especially if the service is open to the browser or external customers.
frequent edge concerns
- CORS
- TLS
- request size caps
- basic throttling
Runtime, Processes and Probes
11RuntimeIn a container environment, it is often easier to process one process per container and scale replicas.
FastAPI docs recommends looking at the deployment context. In orchestrators, it is usually more convenient to run one Uvicorn process per container and scale the number of replicas. It is easier for memory model, ‘lifespan’, graceful shutdown and durability logic around thread state.
routine
- 1 process / container
- replicas for scale
- Workers within the process are not always needed.
12RuntimeIf you use a worker model, remember process semantics.
Uvicorn and Gunicorn solve the problem of process control, but this does not change the fact that every worker has his own memory. For an LLM service, this is important: in-memory state, local caches, and lazily initialized clients must be designed with multiprocess behavior in mind.
textworker A memory != worker B memory
Therefore:
state
shared persistence
shared queues
Carefully with local caches13RuntimeLivenessProbe, ReadinessProbe, StartupProbe solve different problems
Kubernetes docs are very clear about their roles. Readiness is responsible for receiving traffic. Liveness is responsible for restarting a really unhealthy container. Startup probe is useful when the application takes a long time to climb, so that premature checks do not consider it a breakdown.
textThree samples
startupProbe
Let the app start quietly
readinessProbe
Do not allow traffic until the service is ready
livenessProbe
Restart the container if it really freezes or breaks14Runtime‘readyz’ for an LLM service must check not only the process but also the critical wiring
Hello-world readiness can be a formality. For an LLM service, it is useful to check that the main service objects are collected, persistence is available and the application can safely accept new run-s. It doesn’t have to be a full synthetic chat, but it’s not just a ‘return 200’ without meaning.
minimally check
- service objects initialized
- checkpointer reachable
- critical config loaded
15RuntimeGraceful shutdown is especially important for streams and queues
If the replica leaves during a long stream or background job handoff, you need to correctly terminate connections, stop receiving new traffic and not lose your fortune. This is another reason to keep the application state and queue out of the process and respect the lifecycle container.
textstop accepting traffic
finish or safely abort in-flight work
flush logs/traces
exit cleanlyQueueues, Durable Work and Retries
16QueuesLong and heavy LLM tasks often need to be removed from HTTP request lifecycle
If a task can take tens of seconds or minutes, call a lot of tools, generate reports, or do reindex, it’s best to switch to the job/worker pattern. In this case, the HTTP endpoint creates a run and queues the task, and then the client receives status/result in a separate way.
textJOB FLOW
POST /jobs
↓
validate + persist job
↓
enqueue
↓
worker executes graph
↓
status/result store
↓
client polls or streams status17QueuesRetry policy without idempotence quickly turns into a source of duplicates
Queue retry, client retry and internal tool retry are easily overlapped. If side-effect tools are non-idempotent, you can send a letter several times, create a ticket or write off money. Therefore, retry should be designed with idempotency strategy.
routine
- idempotency key
- upsert semantics
- limited retry budget
18QueuesNot only retries, but also dead-letter/failure handling
Some tasks should not be retracted indefinitely: prompt bug, malformed payload, denied external dependency, broken tool schema. In such cases, you need a clear path for a failed job: error status, DLQ or manual parsing, rather than perpetual retry storm.
texttry job
→ retry N times on transient failures
→ mark failed on terminal error
→ send to DLQ / alert / manual review19QueuesInterrupt/resume and queue-based execution are well combined.
Human-in-the-loop flow doesn’t have to live in a synchronized API. Very often, the pause occurs inside the worker-run, and the “resume” comes later. Durable execution and queue here complement each other, not conflict.
textworker executes graph
→ interrupt reached
→ state persisted
→ UI asks human
→ human confirms
→ resume event arrives
→ worker or service continues same thread20QueuesQueue depth is one of the most useful operational metrics.
LLM systems often rely not only on CPU/Memory, but on the accumulation of heavy tasks. Therefore, queue length, age of oldest job, retry count and throughput workers often speak about the health of the system better than just HTTP latency web service.
track down
- queue depth
- oldest job age
- retry rate
- worker success rate
Observability and Budgets
21ObservabilityThree Observability Signals: Logs, Metrics, and Tracks
OpenTelemetry ecosystem is useful precisely because it helps to think not about one “logger”, but about three classes of signals. Logs are good for a specific incident. Metrics are good for aggregate health. Traces are good for a request that has passed through API, graph, tool calls and external dependencies.
textThree Signals
logs, what exactly happened?
How the system behaves on average
How one request went through the system22ObservabilityFor an LLM service, trace must go through HTTP, graph, and tool layer.
A simple request trace is not enough. It is useful to see at least: HTTP span, graph invocation, model call, tool calls and external I/O dependencies. Then you can understand where the latency was born: in the model, retriever-e, tool chain or persistence.
textHTTP request
→ chat service
→ graph run
→ model call
→ tool call
→ db / external api
→ response serialization23ObservabilityNot only latency metrics, but also token/cost metrics.
This is something that teams often forget without LLM experience. Metrics like 'request duration seconds' are useful, but without 'prompt tokens', 'completion tokens', 'tool calls per run', 'cost per request' and 'cost per tenant' picture operational remains blind.
Very useful LLM metrics
- tokens per request
- cost per request
- tool calls per run
- interrupt rate
24ObservabilityCorrelation IDs – mandatory boredom, without which everything breaks down when debunking
It is almost always useful to carry through the system "request id", "thread id", "run id" and "tool call id". Then you can link the log, graph run, streaming session, alert and user complaint into one chain. Without this, debugging an LLM service turns into guesswork.
textrequest_id -> web request
thread_id -> conversation line
run_id -> specific execution
tool_call_id -> tool invocation25ObservabilityThere are two types of budgets: latency budgets and cost budgets.
For an LLM service, it is useful to decide in advance what maximum latency you allow and what price you are willing to pay for the request. This affects model choice, allowed tools, max history length, retry count, and when workflow should be led to queue instead of synchronous response.
textBUDGET THINKING
latency budget
How many seconds can you wait?
cost budget
How many tokens/money can be spent
budget
Form model selection policy and depth of workflow26ObservabilityCareful with cardinality: Don't make log storage metrics
LLM systems are very tempting to drag in labels 'user id', 'prompt hash', 'tool name', 'tenant id', 'thread id' and everything. But high-cardinal metrics quickly become expensive and heavy. Deep context is often best left in logs and tracks rather than metric labels.
rule
- metrics = units
- Traces/logs = detail
- user id as metric label everywhere
Scaling, Rollouts and Rollbacks
27ScalingHPA is a control loop, not a magical continuous elasticity.
Kubernetes HPA periodically looks at metrics and adjusts the desired replica count. This is useful for an LLM service, but it is important to remember that autoscaling is not instantaneous and does not solve everything by itself. If the queue is already crowded or the model provider has hit the rate limit, HPA will not fix the architecture.
textAutoscaling helps when:
More replicas really increase throughput
Autoscaling does not help when:
bottleneck outside your pods
shared provider rate limit
queue policy broken
state only in RAM28ScalingConcurrency limits and backpressure are needed before autoscaling
If a service can accept an infinite number of heavy requests, it will kill itself and external dependencies faster than it can scale. Therefore, it is useful to have limits on parallel runs, queue lengths, and a “fast reject rather than slow meltdown” strategy.

Autoscaling follows measured signals. A concurrency gate and visible queue protect the runtime during the delay between a burst and newly available capacity.
normalization
- max concurrent runs
- max queue depth
- 429 / overload response
- per-tenant limits
29ScalingRolling deploy must respect readiness, otherwise you get false stability.
If the new version starts receiving traffic before it is actually ready, rollout will look “successful” on the processes, but customers will see errors. That’s why readiness probes and the right startup path are so important: they become part of a rollout strategy, not just health endpoints.
textnew pod starts
→ startup probe passes
→ readiness passes
→ only then traffic shifts
→ old pod drains and exits30ScalingRollback should be simple and fast, otherwise it is useless.
Kubernetes Deployment knows how to roll out the previous revision, and this is a very practical idea: the rollback operation should be understandable and cheap in advance. For LLM-service, this also means the versioning of prompt/graph behavior, so that the rollback is not only in the image of the container, but also in the behavior of the system.
worth knowing
- rollback image revision
- rollback prompt/graph revision
- Quickly return to known-good state
31ScalingCanary and staged rollout are particularly useful for LLM behavior changes.
When not only code changes, but also prompt policy, tools, routing logic, or model provider, it’s helpful not to transfer all traffic right away. Even a small percentage of canary can quickly show that latency, cost, or answer quality have left more than expected.
look out
- error rate
- latency
- cost/request
- human review rate
Secrets, MCP and Final Frame
32SecuritySecrets is not just an env vars, but a separate risk zone.
Kubernetes docs specifically emphasize encryption at rest, RBAC, least privilege and external secret stores. For an LLM service, this is especially important because it often has provider API keys, DB creds, vector DB tokens, tool credentials, and sometimes MCP/OAuth secrets.
textA good model for SECRET-OVs
store securely
↓
limit access narrowly
↓
inject into only needed containers
↓
rotate when needed
Secrets should not be smeared by image, code and random env dumps33SecurityLeast privilege is important for both containers and tool credentials
An LLM tool layer often has access to external systems. Therefore, operational security here means not only “hide the key”, but also limit scope, namespace, container access and tools rights. Quick injection quickly turns into blast radius.
limit
- secret scope
- tool permissions
- namespace / tenant access
- avoid overpowered default tokens
34MCPIf the system has an MCP, it must have a separate operational boundary.
MCP transport and authorization are not “tiny parts of tools.” If your service uses remote MCP servers or displays the MCP interface itself, this separate operational surface: transport, auth/OAuth, trusted origins, allowed tools/resources, audit and rate limits should be designed separately from the conventional HTTP product API.
textpublic app API
!=
MCP tool/data plane
They may have:
clientele
different models
rate limits
blast radii35MCPMCP auth should be understood as a full protocol concern.
The current MCP specification already describes the authorization flow separately. For production, this means that if you connect a remote MCP server, don’t leave auth for later. The protocol has its own security story, and it needs to be given as much attention as the bearer auth of a conventional FastAPI API.
remember
- auth flow is protocol-level
- token handling matters
- separate trust boundary
36Ops FrameThe main summary of the volume
For an LLM service to live, it is not enough to “raise FastAPI.” Need: edge with correct HTTPS/proxy behavior, honest probes, external durable state, queue for long tasks, tracing and budgets, controlled scaling/rollback and neat work with secrets and MCP boundary. This is the real devops framework around an LLM application.
textFULL DEVOPS FRAME
edge + TLS + proxy correctness
+
runtime probes + graceful lifecycle
+
external state + queues
+
observability + budgets
+
scaling + rollback
+
secrets + MCP security
=
Live LLM service, not local demoNo dead end
Keep moving through the map.
Continue in sequence, switch to a related guide, or return to the seven-track learning map.