VPS Tutorials
Deploy Langflow on a VPS (2026): Complete Tutorial for Docker Compose, PostgreSQL, Login, API and HTTPS
Following Langflow's current official deployment method, this tutorial explains VPS sizing, Docker Compose, PostgreSQL, login, HTTPS, the Flow API, upgrades and backups.

Langflow is for people who want to drag prompts, models, tools and data-processing steps into an executable workflow. It is not a local large language model, and it does not magically eliminate model costs; an ordinary VPS runs the visual editor, the API and the database, while the actual inference is done by OpenAI, Anthropic, OpenRouter, LiteLLM or another Ollama server.
This guide deploys the stable release v1.11.1 published on July 28, 2026. The final architecture is Langflow + PostgreSQL + Caddy, with neither 7860 nor 5432 exposed directly to the public internet. The goal is not to briefly open a demo page, but to configure login, HTTPS, the Flow API, upgrades and backups together.
First, the sizing conclusions:
- One person building flows with low-frequency API calls: 2 cores, 4GB and a 40GB SSD is enough to start;
- Shared by 3–10 people, or flows with heavier document processing: 4 cores, 8GB and an 80GB SSD is recommended;
- An ordinary CPU VPS is not suitable for running large models locally, and in particular do not split 4GB of memory between Langflow, PostgreSQL and Ollama at the same time;
- If you only want to try it for ten minutes, a single container with SQLite works; if you plan to use it long term, go straight to PostgreSQL.
These three tools overlap, but their focus differs:
| Tool | Better suited for | Deployment characteristics |
|---|---|---|
| Langflow | Quickly assembling Agents, model chains and callable APIs with nodes | Python ecosystem, many components, intuitive Flow debugging |
| Dify | Building AI applications with a knowledge base, an admin console and an end-user interface | More services, a full deployment consumes more resources |
| Flowise | Those familiar with LangChain who want to build chat flows quickly | Node.js ecosystem, straightforward to get started |
If your focus is RAG applications and operational management, see the Dify private knowledge base deployment tutorial. Readers who prefer Node.js and LangChain can compare the Flowise visual AI workflow tutorial. This article only covers a single-machine production deployment of Langflow.
Langflow itself is not the most resource-hungry part. Memory rises noticeably when flows load components, parse files and wait for multiple model responses; PostgreSQL, the system cache and Docker also need room.
| Usage | Recommended configuration | Notes |
|---|---|---|
| Personal learning, a few Flows | 2 cores, 4GB | Run a small number of flows at a time, no local inference |
| Small team, more document processing | 4 cores, 8GB | Leave headroom for the database and concurrent tasks |
| Multiple Workers, high-concurrency API | 8GB and up, add Redis | Beyond the lightweight single-machine scope of this article |
| Local model inference | A separate GPU machine | Langflow only connects to model APIs |
If you haven't decided on a size yet, see How to choose a VPS configuration. 2 cores and 4GB is a starting point, not a concurrency promise.
In this architecture, only Caddy is exposed to the public internet:
浏览器 / API 客户端
│ HTTPS 443
▼
Caddy
│ 127.0.0.1:7860
▼
Langflow ──────► OpenAI / Anthropic / OpenRouter / LiteLLM
│
│ Docker 内部网络
▼
PostgreSQL:5432
PostgreSQL has no host port mapping. Langflow binds only to 127.0.0.1:7860, so even if UFW is misconfigured, the editor will not be directly exposed on the public IP.
This article uses Ubuntu 24.04. First point the A/AAAA records of langflow.example.com at the VPS. Replace the example domain with your own; don't copy it verbatim.
Install the basic tools:
sudo apt update
sudo apt install -y ca-certificates curl openssl ufw
Install Docker from the official repository:
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" |
sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
The firewall only allows SSH, HTTP and HTTPS:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
When working remotely, always allow OpenSSH before enabling UFW. Do not open 7860 or 5432.
sudo mkdir -p /opt/langflow
sudo chown "$USER":"$USER" /opt/langflow
cd /opt/langflow
First generate three random values: the database password, the admin password and the encryption key. The commands below only write to variables in the current terminal and are not automatically saved to a history file:
langflow_db_password="$(openssl rand -hex 24)"
langflow_admin_password="$(openssl rand -hex 24)"
langflow_secret_key="$(python3 -c 'from secrets import token_urlsafe; print(token_urlsafe(32))')"
Create .env:
umask 077
cat >.env <<EOF
POSTGRES_USER=langflow
POSTGRES_PASSWORD=${langflow_db_password}
POSTGRES_DB=langflow
LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER_PASSWORD=${langflow_admin_password}
LANGFLOW_SECRET_KEY=${langflow_secret_key}
EOF
chmod 600 .env
Put the admin password in your password manager, then clear the terminal variables:
unset langflow_db_password langflow_admin_password langflow_secret_key
LANGFLOW_SECRET_KEY is used to encrypt Credentials in the database. If it is lost, even if the database still exists, the model keys already saved may no longer be decryptable, so .env must be backed up together with the database.
Create /opt/langflow/docker-compose.yml:
services:
postgres:
image: postgres:16-trixie
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- langflow-postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
langflow:
image: langflowai/langflow:v1.11.1
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:7860:7860"
environment:
LANGFLOW_DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
LANGFLOW_CONFIG_DIR: /app/langflow
LANGFLOW_AUTO_LOGIN: "False"
LANGFLOW_SUPERUSER: ${LANGFLOW_SUPERUSER}
LANGFLOW_SUPERUSER_PASSWORD: ${LANGFLOW_SUPERUSER_PASSWORD}
LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY}
LANGFLOW_OPEN_BROWSER: "False"
LANGFLOW_LOG_LEVEL: INFO
DO_NOT_TRACK: "True"
volumes:
- langflow-data:/app/langflow
healthcheck:
test:
- CMD-SHELL
- >-
python -c "import urllib.request;
urllib.request.urlopen('http://127.0.0.1:7860/health_check', timeout=5)"
interval: 30s
timeout: 10s
retries: 10
start_period: 90s
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
volumes:
langflow-postgres:
langflow-data:
This uses the official v1.11.1 image and postgres:16-trixie. The official example notes that pinning PostgreSQL's Debian base version avoids glibc collation warnings after the underlying system changes silently.
First render the Compose configuration:
chmod 600 .env docker-compose.yml
sudo docker compose config --quiet
sudo docker compose config --images
Do not paste the full docker compose config output into forums or tickets; it expands the database and admin passwords.
To understand why healthchecks, log rotation and restart policies are configured this way, continue with the Docker Compose production configuration checklist.
cd /opt/langflow
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
The first start has to create database tables and load components, so it takes longer than a typical static site. Keep watching the logs:
sudo docker compose logs -f --tail=200 langflow postgres
Once the service looks healthy, press Ctrl+C to exit the logs, then check the health endpoint:
curl -fsS http://127.0.0.1:7860/health_check
sudo docker compose ps
sudo ss -lntp | grep -E ':7860|:5432'
The health endpoint should return the database and chat service status. In the listening list, 7860 must only be 127.0.0.1:7860; 5432 should not appear among the host's listening ports.
Install Caddy:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key |
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt |
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
Edit /etc/caddy/Caddyfile:
langflow.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:7860 {
transport http {
read_timeout 10m
}
}
}
Longer flows may wait for models or tools to execute, so the reverse proxy read timeout is raised to 10 minutes here. After saving, validate and reload:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://langflow.example.com
If certificate issuance fails, first confirm that DNS points to the current VPS and that 80/443 are not occupied by another program. Full troubleshooting steps are in the Caddy reverse proxy and automatic HTTPS tutorial.
Open https://langflow.example.com and log in with the LANGFLOW_SUPERUSER from .env and the admin password. Do not change auto-login back to True; a public editor without login protection will be found by scanners quickly.
Once inside, complete four things first:
- Create a new project so later Flows don't all pile up in the default directory;
- Create a minimal chat Flow, starting from the Basic Prompting template;
- Create a new Credential in the model component and fill in an OpenAI, Anthropic or OpenRouter Key;
- Send a test message in the Playground to confirm the input, model and output nodes all work.
Model Keys are stored in Langflow global variables or Credentials and encrypted by LANGFLOW_SECRET_KEY. Do not write real Keys directly into exported Flow JSON, and do not hardcode Keys into the Compose file in a tutorial.
If your team already has a unified model gateway, you can connect LiteLLM. That lets you centralize virtual Keys, budgets and model routing; see the LiteLLM unified LLM API gateway tutorial for deployment.
Once the Flow works in the Playground, open the Flow's API panel and copy the Flow ID. Then go to the API Keys page in personal settings and create a Langflow API Key.
Set the variables on the calling machine:
export LANGFLOW_SERVER_URL="https://langflow.example.com"
export LANGFLOW_API_KEY="粘贴刚创建的 Langflow API Key"
export LANGFLOW_FLOW_ID="粘贴 Flow ID"
Call the v1 Flow API:
curl --request POST \
--url "${LANGFLOW_SERVER_URL}/api/v1/run/${LANGFLOW_FLOW_ID}?stream=false" \
--header "Content-Type: application/json" \
--header "x-api-key: ${LANGFLOW_API_KEY}" \
--data '{
"input_value": "用三句话解释什么是 VPS",
"output_type": "chat",
"input_type": "chat"
}'
If the real Flow needs tweaks, rely on the code generated automatically by the API panel. Node IDs differ between components, so don't blindly copy them from someone else's example.
A Langflow API Key should only be placed on the server side, in CI Secrets or in a secure automation platform. Once written into browser JavaScript, any visitor can take it from the developer tools.
Do not use latest in production. Before upgrading, read the new version's Release Notes, confirm database migration and environment variable changes, then do the full backup described in the next section.
Check the current image:
cd /opt/langflow
sudo docker compose images
Change v1.11.1 in docker-compose.yml to the new stable version you have confirmed, then:
sudo docker compose pull langflow
sudo docker compose up -d langflow
sudo docker compose logs --tail=300 langflow postgres
curl -fsS http://127.0.0.1:7860/health_check
If the upgrade involves a database migration, simply changing the image tag back to v1.11.1 may not roll it back. The safe approach is to restore the pre-upgrade PostgreSQL dump, the configuration directory and the old image version together.
A complete backup includes at least:
- The PostgreSQL database;
- The
langflow-dataVolume; .envanddocker-compose.yml;- The Caddyfile;
- The current Langflow image version.
Create a backup directory and export PostgreSQL:
cd /opt/langflow
backup_dir="backups/$(date +%F-%H%M%S)"
mkdir -p "$backup_dir"
sudo docker compose exec -T postgres sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
>"$backup_dir/langflow-postgres.dump"
cp .env docker-compose.yml "$backup_dir/"
sudo cp /etc/caddy/Caddyfile "$backup_dir/Caddyfile"
sudo chown -R "$USER":"$USER" "$backup_dir"
chmod 600 "$backup_dir/.env"
sudo docker compose images >"$backup_dir/docker-images.txt"
Briefly stop Langflow writes, then archive the configuration Volume:
sudo docker compose stop langflow
langflow_data_volume="$(sudo docker volume ls --format '{{.Name}}' | grep '_langflow-data$' | head -1)"
test -n "$langflow_data_volume"
sudo docker run --rm \
-v "${langflow_data_volume}:/source:ro" \
-v "$PWD/$backup_dir:/backup" \
alpine tar -czf /backup/langflow-data.tar.gz -C /source .
sudo docker compose up -d langflow
Sync the backup to another machine or object storage. A backup on the same VPS disk only protects against accidental deletion, not whole-machine failure. For a more complete off-site plan, see VPS backup and restore drill.
Rehearse restoration on a test VPS first. Prepare the same version of .env and Compose, start PostgreSQL, then restore:
cat langflow-postgres.dump |
sudo docker compose exec -T postgres sh -c \
'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists'
Then restore langflow-data and finally start Langflow. The key and the database must match; you cannot restore only one of them.
sudo docker compose ps
curl -v http://127.0.0.1:7860/health_check
sudo systemctl status caddy --no-pager
sudo journalctl -u caddy -n 100 --no-pager
If port 7860 works locally but the domain doesn't open, the problem is usually DNS, Caddy or the firewall. If it doesn't work locally either, check the Langflow logs.
sudo docker compose ps postgres
sudo docker compose logs --tail=200 postgres langflow
sudo docker compose exec postgres sh -c \
'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
Focus on whether the database password contains manually added URI special characters, whether the rendered Compose service name is postgres, and whether an old Volume retained a different password. This article uses hexadecimal random passwords, which avoids connection-string escaping issues.
Confirm that the four variables exist in .env, but do not output their values:
for variable_name in \
LANGFLOW_AUTO_LOGIN LANGFLOW_SUPERUSER LANGFLOW_SUPERUSER_PASSWORD LANGFLOW_SECRET_KEY; do
sudo docker compose exec -T langflow sh -c \
'test -n "$(printenv "$1")"' sh "$variable_name" &&
echo "$variable_name 已设置"
done
In this article LANGFLOW_AUTO_LOGIN is written in Compose, not in .env. So confirm the first item from the rendered configuration:
sudo docker compose config | grep 'LANGFLOW_AUTO_LOGIN: "False"'
The admin account is only created during initialization. When an old database already exists, changing .env does not automatically reset an existing user's password; don't repeatedly delete containers hoping for luck, and above all don't delete the database Volume.
Check whether the request header is x-api-key and whether the value is the Langflow API Key rather than an OpenAI Key. Also confirm the API Key has no extra copied spaces:
test -n "$LANGFLOW_API_KEY" && echo "LANGFLOW_API_KEY 已设置"
curl -i "${LANGFLOW_SERVER_URL}/api/v1/version" \
-H "x-api-key: ${LANGFLOW_API_KEY}"
sudo docker compose logs -f --tail=200 langflow
free -h
sudo docker stats --no-stream
If the logs show a model API timeout, check the model provider and outbound network first; if the container was OOM Killed, upgrade memory or reduce the number of flows running at once. Don't just raise the Caddy timeout to an hour right away; that only hides the real error for longer.
- Langflow is pinned to v1.11.1 and does not use
latest; -
LANGFLOW_AUTO_LOGIN=False, and the admin password is long enough; -
LANGFLOW_SECRET_KEYis backed up together with PostgreSQL; - 7860 binds only to
127.0.0.1, and 5432 has no host port; - The
https://langflow.example.comcertificate is valid; - The Playground can complete a real model call;
- A Flow API request with
x-api-keysucceeds; - The database, configuration Volume,
.envand Caddyfile all have off-site backups; - The image version is recorded before upgrading, and a test restore has been done.
Langflow is best placed "in front of the model API": an ordinary VPS runs the editor, flows and database, while the GPU or commercial models sit at the other end. 2 cores and 4GB is enough for an individual to start, but small teams shouldn't skimp on that 4GB of memory. What really determines whether you can use it long term is not how pretty the nodes look, but whether login, keys, the database and the recovery process are done properly.