---
title: "How to Use the LangGraph CLI to Build and Run an Agent Server in Docker"
description: "Every LangGraph CLI command run for real on version 0.4.33, from langgraph new to a Docker image, plus the two traps the docs skip: a build that copies your .env file and a Python 3.11 default base image."
author: "Jahanzaib Ahmed"
date: 2026-10-10
category: "ai-agents"
readingTime: "13 min read"
tags: ["langgraph", "langgraph cli", "docker", "ai-agents", "python"]
canonical: https://www.jahanzaib.ai/blog/langgraph-cli
source: https://www.jahanzaib.ai
---
# How to Use the LangGraph CLI to Build and Run an Agent Server in Docker

When I built a LangGraph project with the LangGraph CLI and no `.dockerignore`, my `.env` file ended up inside the Docker image. This guide walks the whole CLI, from `langgraph new` to a deployable image, and shows how to close that hole and one other. Steps 1 to 6 cost nothing and need no account. It's for Python developers who already have a graph and now need to serve it over HTTP.

I ran every command below on October 10, 2026, with [langgraph-cli 0.4.33](https://pypi.org/project/langgraph-cli/), langgraph 1.2.14 and langgraph-api 0.16.0. The output blocks are copied from that run, not from the docs. The second trap: the default base image is Python 3.11, whatever Python you develop on.

## Before you start

-   **Python 3.11 or newer.** The CLI installs on 3.10, but the dev server package (`langgraph-api`) requires 3.11 or later.
-   **Docker** with the daemon running, for `build`, `up` and local `deploy` builds. `dev` and `dockerfile` don't need it.
-   **uv or pip.** I use [uv](https://docs.astral.sh/uv/). Step 1 shows the pip equivalent.
-   **A LangSmith API key**, only for `langgraph up` and `langgraph deploy`. Steps 1 to 6 need no account at all.
-   **Cost:** $0 locally. Deploying to LangSmith needs the Plus plan at $39 per seat per month, which includes one free small serverless deployment ([LangChain pricing](https://www.langchain.com/pricing)).

The commands map to stages of a project, and each stage needs more than the one before it. The diagram below shows the order I use them in.

![LangGraph CLI command pipeline from langgraph new through dev, build and up to langgraph deploy](https://cdn.sanity.io/images/qajb7q5q/production/4d09f26ebe3aba04789c2bb9300a49931563cbaf-1376x768.png?w=1200&q=75&auto=format&fit=max)

_Everything left of langgraph build runs without Docker or an account. The build step is where your folder's contents become a shippable artifact, so it's where most mistakes get baked in._

## Step 1: Install the LangGraph CLI with the dev server extra

The package comes in two sizes. Plain `langgraph-cli` gives you `build`, `up`, `dockerfile` and `deploy`. The `[inmem]` extra adds the local server that `langgraph dev` runs. Install it into a fresh virtual environment:

```
uv venv -p 3.12 .venv
source .venv/bin/activate
uv pip install "langgraph-cli[inmem]" langgraph
langgraph --version
```

Without uv, the same thing is `python3 -m venv .venv`, then `source .venv/bin/activate` and `pip install "langgraph-cli[inmem]" langgraph`. You should see `LangGraph CLI, version 0.4.33` or newer. Run `langgraph --help` and check that the list includes `dev`, `build`, `up`, `dockerfile`, `deploy`, `new` and `validate`. The `validate` command doesn't appear in the quick commands table of the [official CLI reference](https://docs.langchain.com/langsmith/cli), but it's there and it's useful.

## Step 2: Scaffold a project with langgraph new

Run `langgraph new` with an explicit template so you skip the interactive prompt:

```
langgraph new --template new-langgraph-project-python triage-agent
cd triage-agent
```

The CLI downloads the [new-langgraph-project](https://github.com/langchain-ai/new-langgraph-project) repo as a zip. The templates on offer in 0.4.33 are `new-langgraph-project-python`, `agent-python`, `deep-agent-python`, `new-langgraph-project-js` and `deep-agent-js`. The file that matters is `langgraph.json`, which every other command reads:

```
{
  "$schema": "https://langgra.ph/schema.json",
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent/graph.py:graph"
  },
  "env": ".env",
  "image_distro": "wolfi"
}
```

`graphs` maps a graph ID (here `agent`) to a file and variable. That ID becomes the `assistant_id` you call over HTTP. `dependencies: ["."]` tells the build to install the folder's `pyproject.toml`. And `image_distro: "wolfi"` picks the base image the docs recommend as smaller and more secure.

## Step 3: Put your graph in and validate langgraph.json

Replace `src/agent/graph.py` with your own graph. For this guide I used a ticket router that calls no model, so you can run every step without an API key or a bill. Swap the `classify` function for a model call once the plumbing works.

```
from typing import Literal
from langgraph.graph import END, START, StateGraph
from typing_extensions import TypedDict


class State(TypedDict, total=False):
    message: str
    route: str
    reply: str


def classify(state: State) -> State:
    text = state["message"].lower()
    if any(w in text for w in ("invoice", "refund", "charge", "billing")):
        return {"route": "billing"}
    if any(w in text for w in ("error", "crash", "broken", "bug")):
        return {"route": "bug"}
    return {"route": "general"}


def pick(state: State) -> Literal["billing", "bug", "general"]:
    return state["route"]


def billing(state: State) -> State:
    return {"reply": "Sent to billing. Expect an answer within one business day."}


def bug(state: State) -> State:
    return {"reply": "Logged as a bug. Please include the exact error text."}


def general(state: State) -> State:
    return {"reply": "Thanks, a person will read this today."}


builder = StateGraph(State)
for name, fn in (("classify", classify), ("billing", billing),
                 ("bug", bug), ("general", general)):
    builder.add_node(name, fn)
builder.add_edge(START, "classify")
builder.add_conditional_edges("classify", pick)
for node in ("billing", "bug", "general"):
    builder.add_edge(node, END)

graph = builder.compile(name="Ticket Triage")
```

Export the compiled graph, not the builder. The [Agent Server docs](https://docs.langchain.com/langsmith/agent-server) recommend this because the server loads a compiled graph once at startup and reuses it for every run. Then check the config:

```
langgraph validate
```

```
Configuration file .../triage-agent/langgraph.json is valid. (1 graph found)

  warning: Unknown key '$schema' is not a recognized config field.
```

That warning comes from the template's own `$schema` line and is harmless: the key only gives your editor autocomplete. A real problem, such as a wrong path in `graphs`, fails here in a second instead of halfway through a Docker build.

## Step 4: Run the graph locally with langgraph dev

Install the project into your environment, then start the dev server:

```
uv pip install -e .
langgraph dev --no-browser
```

```
- API: http://127.0.0.1:2024
- Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
- API Docs: http://127.0.0.1:2024/docs

This in-memory server is designed for development and testing.
```

This is the same Agent Server API you'd get in production: assistants, threads, runs and state, served from one Python process with hot reloading. It saves state to a `.langgraph_api` folder in your project ([data storage docs](https://docs.langchain.com/langsmith/data-storage-and-privacy)), which matters again in step 5. If you want the visual debugger on top of it, my [LangGraph Studio setup guide](https://www.jahanzaib.ai/blog/langgraph-studio-setup-debug-agents) covers that side.

Prove it works over HTTP rather than trusting the banner. Create a thread, then run the graph on it and wait for the result:

```
THREAD=$(curl -s -X POST http://127.0.0.1:2024/threads \
  -H "Content-Type: application/json" -d '{}' | jq -r .thread_id)

curl -s -X POST http://127.0.0.1:2024/threads/$THREAD/runs/wait \
  -H "Content-Type: application/json" \
  -d '{"assistant_id": "agent", "input": {"message": "I was charged twice on my last invoice"}}'
```

```
{"message":"I was charged twice on my last invoice","route":"billing","reply":"Sent to billing. Expect an answer within one business day."}
```

No `jq`? Copy the `thread_id` value from the first response by hand. A `GET /threads/$THREAD/state` returns the same values plus metadata, including `"langgraph_api_version":"0.16.0"`. If the run hangs here, it'll hang in Docker too, so fix it now.

## Step 5: Add a .dockerignore before you build anything

The template ships without a `.dockerignore`, and the generated Dockerfile starts with `ADD . /deps/triage-agent`, which copies the entire folder into the image. Docker reads `.dockerignore`, not `.gitignore` ([Docker build context docs](https://docs.docker.com/build/concepts/context/)).

Plenty of people keep their virtual environment inside the project folder, so I tested that case. I created a `.venv` in `triage-agent/`, ran `langgraph build -t triage-agent:withvenv` with no `.dockerignore`, then listed what landed in the image:

```
docker run --rm --entrypoint ls triage-agent:withvenv -a /deps/triage-agent
```

```
. .. .codespellignore .env .env.example .github .gitignore .langgraph_api .venv Dockerfile LICENSE Makefile README.md agent.egg-info langgraph.json pyproject.toml src static tests uv.lock
```

**Warning:** `.env` is in that list. Every API key in it is now inside an image you might push to a registry. The `.langgraph_api` folder came along too, carrying the threads from your local test runs.

Create the file in the project root:

```
.venv
.env
.langgraph_api
__pycache__
.git
tests
```

I rebuilt with exactly that file and the same Python 3.12 base (step 6 shows how to pin it). The image dropped from 1.04 GB to 848 MB, and `.env`, `.venv` and `.langgraph_api` were all gone from `/deps/triage-agent`. Secrets belong in the runtime environment (the `env` key, `docker run -e` or your platform's secret store), never in a layer.

## Step 6: Pin the Python version and build the image

Look at the first line of the Dockerfile the CLI generates:

```
langgraph dockerfile Dockerfile
head -1 Dockerfile
```

```
FROM langchain/langgraph-api:3.11-wolfi
```

I developed on Python 3.12, and the image still targets 3.11. The CLI reference confirms `python_version` accepts `3.11`, `3.12` or `3.13` and defaults to 3.11. Code that uses newer syntax or a package that dropped 3.11 will pass every local test and then fail in the container. Add the key to `langgraph.json` to match what you develop on:

```
"python_version": "3.12"
```

Run `langgraph dockerfile Dockerfile` again and the base becomes `langchain/langgraph-api:3.12-wolfi`. The `Dockerfile` you wrote is only for reading or for your own pipeline; `langgraph build` generates its own, so delete it if you don't need it. Now build:

```
langgraph build -t triage-agent:local
```

On my Mac mini the very first build (on the default 3.11 base) took 39 seconds, and a rebuild with cached layers took 10. Both ended with `Successfully tagged triage-agent:local`. Check it with `docker images triage-agent` and confirm the interpreter with `docker run --rm --entrypoint python triage-agent:local --version`, which printed `Python 3.12.15` after the pin. Pass `--platform linux/amd64` if you build on Apple silicon for an x86 server.

If your project has a `uv.lock`, consider `"source": {"kind": "uv"}` in place of `dependencies`. The docs note that `dependencies: ["."]` resolves packages again at build time and ignores the lockfile, so two builds a week apart can ship different versions.

## Step 7: Start the full stack with langgraph up

`langgraph up` runs your production image locally. It builds the image and starts three containers: the Agent Server, Postgres for threads and checkpoints, and Redis, which the [Agent Server docs](https://docs.langchain.com/langsmith/agent-server) describe as carrying signalling, cancellation and streaming between API servers and queue workers.

![Diagram of the local stack langgraph up starts: Agent Server behind a license check, with Postgres and Redis](https://cdn.sanity.io/images/qajb7q5q/production/0e645a0f81a69f7879e8eabb865250ebc34a375e-1376x768.png?w=1200&q=75&auto=format&fit=max)

_Postgres holds everything durable; Redis only moves messages between processes. The license check runs before the server accepts a request, so the containers can start healthy and the server still exits._

```
export LANGSMITH_API_KEY=lsv2_...
langgraph up --wait --port 8123
```

I ran it without a key first, because I wanted to see the failure you'd hit. Postgres and Redis came up healthy, then the API container exited with code 3. Its log, via `docker logs triage-agent-langgraph-api-1`:

```
ValueError: License verification failed. Please ensure proper configuration:
- For local development, set a valid LANGSMITH_API_KEY for an account with LangGraph Cloud access in the environment defined in your langgraph.json file.
- For production, configure the LANGGRAPH_CLOUD_LICENSE_KEY environment variable with your LangGraph Cloud license key.
```

So `up` is not a free way to self host. The [local development guide](https://docs.langchain.com/langsmith/local-dev-testing) says it needs a LangSmith API key for local testing and a license key for production use. I had no LangSmith key on this machine, so I couldn't confirm whether a key from the free Developer plan passes the check. The error message asks for "an account with LangGraph Cloud access", so test your key here before you plan around it. With a valid key in `.env` (which `langgraph.json` already points at) or exported in your shell, the server answers on `http://localhost:8123`. The same `runs/wait` call from step 4 works against it. Run `langgraph up --recreate` before you deploy anything, so you test a fresh build and not cached containers.

Here's how the commands compare on what they need and where state ends up:

| Command | Needs Docker | Needs an account | Default port | State lives in |
| --- | --- | --- | --- | --- |
| langgraph dev | No | No | 2024 | .langgraph_api folder |
| langgraph dockerfile | No | No | None | None |
| langgraph build | Yes | No | None | None |
| langgraph up | Yes | LangSmith key, or a license for production | 8123 | Postgres container |
| langgraph deploy | Optional (builds remotely without it) | LangSmith key with Deployments access | Managed | Managed Postgres |

## Step 8: Deploy with langgraph deploy

`langgraph deploy` builds the image, pushes it to a managed registry and creates or updates a LangSmith deployment in one command. It's marked beta, and the CLI reference says it isn't yet supported on LangSmith Cloud hosted in AWS. The key needs access to Deployments, which the pricing page lists on Plus and above, not on the free Developer plan. If Docker isn't installed, it builds remotely. The `.dockerignore` from step 5 still applies, because the build reads the same folder.

```
langgraph deploy --name triage-agent
langgraph deploy logs --name triage-agent --follow
langgraph deploy list
```

LangChain's own walkthrough shows the full loop, from scaffold to Studio to a live deployment:

[Video: Deploy CLI: The Easiest Way to Deploy Agents from Your Terminal](https://www.youtube.com/watch?v=hcWHufkzicc)

_LangChain's 8 minute demo. Skip to 3:50 for the deploy command itself and to 6:11 for logs, list and delete._

If you host somewhere else, stop at step 6. Your image is a normal container. But read step 7 again first: the Agent Server inside it still runs the license check, so self hosting it in production means a license key, not just a server. LangChain's pricing page lists self hosted deployment under the Enterprise plan.

## Troubleshooting the LangGraph CLI

**Error: Required package 'langgraph-api' is not installed.** You installed plain `langgraph-cli` and ran `langgraph dev`. I reproduced this in a clean environment, and the CLI prints the fix itself: `pip install -U "langgraph-cli[inmem]"`.

**The API container exits with code 3 after `langgraph up`.** Postgres and Redis show healthy, the server doesn't. That's the license check in step 7. Run `docker logs` on the API container and you'll see `License verification failed`. Set `LANGSMITH_API_KEY` in the env file named in `langgraph.json`.

**Port already in use.** `up` wants 8123, 5432 and 6379, per the local development guide. Check with `lsof -i :5432`. A local Postgres is the usual offender. Move the API with `--port`, or point at an existing database with `--postgres-uri`.

**"Docker Compose requires buildx plugin to be installed."** I got this warning during `up` and the build still finished. It's safe to ignore locally. Install buildx if you need multi platform builds.

**The image works locally and breaks in production on import.** Check the first `FROM` line. If it says 3.11 and you develop on something newer, set `python_version` (step 6).

**A graph change doesn't show up in `up`.** Unlike `dev`, `up` doesn't hot reload unless you pass `--watch`. Use `--recreate` to force a rebuild.

## What it costs once deployed, and when to skip it

Beyond the free serverless deployment on Plus, LangSmith meters runtime compute at 0.0675 LSU per vCPU hour, and one LSU costs $1.00, per the [pricing page](https://www.langchain.com/pricing). The same page's FAQ says serverless deployments scale to zero when idle and recommends dedicated deployments for any customer facing agent.

Most CLI commands also send one analytics event with your OS, Python version, CLI version and the command name, according to the data storage docs linked in step 4. Set `LANGGRAPH_CLI_NO_ANALYTICS=1` to turn it off. On anything regulated, I'd set it in the shell profile on day one.

If you'd rather run plain LangGraph inside your own FastAPI service, you give up the Agent Server API and manage the checkpointer yourself. My guide to the [LangGraph checkpointer on Postgres](https://www.jahanzaib.ai/blog/langgraph-checkpointer-postgres) covers that route. Durable pauses for approval are in [adding human approval to a LangGraph workflow](https://www.jahanzaib.ai/blog/langgraph-human-in-the-loop).

And when not to use it at all: if your agent is one request in and one response out, with no threads, no background runs and no streaming, the Agent Server is more machinery than you need. A small API around `graph.invoke()` is simpler, and it has no license check. I reach for the CLI when the project needs threads that survive restarts and runs that keep going after the HTTP call returns. If you're still deciding whether [LangGraph](https://www.jahanzaib.ai/glossary/langgraph) fits at all, start with [LangChain versus LangGraph](https://www.jahanzaib.ai/blog/langchain-vs-langgraph) or the broader [LangGraph production tutorial](https://www.jahanzaib.ai/blog/langgraph-tutorial-build-production-ai-agents). Once it's deployed, turn on [tracing](https://www.jahanzaib.ai/glossary/tracing) before real traffic arrives.

If you'd rather have an agent like this built, deployed and monitored for you, I do that for businesses: [see how I build AI agents](https://www.jahanzaib.ai/agents).

## Frequently asked questions

### What is the LangGraph CLI?

It's the official command line tool for running LangGraph graphs as an Agent Server. It scaffolds projects (`new`), runs a local dev server (`dev`), builds Docker images (`build`), generates Dockerfiles (`dockerfile`), starts a local Postgres and Redis stack (`up`) and deploys to LangSmith (`deploy`). Every command reads a `langgraph.json` file in the project folder.

### What is the difference between langgraph dev and langgraph up?

`langgraph dev` runs the server as one Python process with hot reloading on port 2024, saves state to a local folder and needs neither Docker nor an account. `langgraph up` builds a Docker image and runs it with Postgres and Redis on port 8123. It needs Docker and a LangSmith API key, and it is the closer match to production.

### Is the LangGraph CLI free?

The CLI is open source on PyPI, and `dev`, `dockerfile` and `build` work without any account. `langgraph up` needs a LangSmith API key, and production self hosting needs a license key. Deploying to LangSmith needs the Plus plan at $39 per seat per month, which includes one free small serverless deployment.

### How do I install langgraph-cli?

Run `pip install "langgraph-cli[inmem]"` in a Python 3.11 or newer environment if you want the local dev server. Plain `pip install langgraph-cli` is enough for `build`, `up` and `deploy`, but `langgraph dev` will then fail with a missing `langgraph-api` error. Check the install with `langgraph --version`.

### Can I self host a LangGraph CLI image without LangSmith?

You can build and run the image anywhere Docker runs, but the Agent Server inside it checks for a license when it starts. In my test it exited with "License verification failed" when no key was set. The docs say local testing works with a LangSmith API key. Production self hosting needs a LangGraph license key, and LangChain lists self hosted deployment under its Enterprise plan.

## Related

- [How to Use MCP Servers in LangGraph (and Serve a Graph as One)](https://www.jahanzaib.ai/blog/langgraph-mcp)
- [How to Inspect LangGraph Deep Agents: What create_deep_agent Adds](https://www.jahanzaib.ai/blog/langgraph-deep-agents)
- [How to Run LangGraph Examples With Real Output](https://www.jahanzaib.ai/blog/langgraph-examples)

---

Canonical HTML version: https://www.jahanzaib.ai/blog/langgraph-cli
