feat: compose stack in devcontainer (db, langflow, lfx)

- docker-compose.yml with app/db/langflow/lfx services and healthchecks
- Dockerfile.lfx (stateless flow runtime on :6004)
- init-db.sql (langflow + test databases)
- devcontainer.json switched to compose mode with runServices
- setup.sh waits for compose services and requires .env
This commit is contained in:
Jonas Miederer 2026-09-06 22:23:30 +00:00
parent 86fe4424e7
commit 2ba062c4c5
6 changed files with 154 additions and 9 deletions

View file

@ -0,0 +1,19 @@
# Stateless Langflow flow runtime (LFX). Serves versioned flow JSON files
# deployed by our backend via POST /flows/upload/ (persisted in /flows).
# LFX stays on the internal compose network; our FastAPI is the only
# public surface and proxies run/stream calls.
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# lfx = flow runtime; requests = convenience for custom-component code.
RUN pip install "lfx==1.12.0" requests
RUN mkdir -p /flows
VOLUME /flows
EXPOSE 6004
# Serve the /flows dir (uploaded flows persist there via --flow-dir).
# Flags verified against `lfx serve --help` (lfx 1.12.0).
CMD ["lfx", "serve", "--host", "0.0.0.0", "--port", "6004", "--flow-dir", "/flows", "/flows"]

View file

@ -1,15 +1,10 @@
{
"name": "kirche-einfach",
"build": {
"dockerfile": "Dockerfile",
"args": {
"FORGEJO_USERNAME": "jonas"
}
},
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"runServices": ["db", "langflow", "lfx"],
"mounts": [
"source=${localEnv:USERPROFILE}/.ssh,target=/mnt/host-ssh,type=bind,readonly",
"source=kirche-einfach-venv,target=/workspaces/kirche-einfach/backend/.venv,type=volume",
"source=kirche-einfach-node-modules,target=/workspaces/kirche-einfach/frontend/node_modules,type=volume"
"source=${localEnv:USERPROFILE}/.ssh,target=/mnt/host-ssh,type=bind,readonly"
],
"remoteUser": "vscode",
"containerEnv": {

View file

@ -0,0 +1,91 @@
# Compose stack for the kirche-einfach devcontainer.
# VS Code attaches to the `app` service (see devcontainer.json); all values
# come from the repo-root `.env` via `env_file` (no ${} interpolation needed).
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
FORGEJO_USERNAME: jonas
command: sleep infinity
volumes:
- ..:/workspaces/kirche-einfach:cached
- kirche-einfach-venv:/workspaces/kirche-einfach/backend/.venv
- kirche-einfach-node-modules:/workspaces/kirche-einfach/frontend/node_modules
env_file:
- ../.env
ports:
- "8000:8000" # FastAPI
- "3000:3000" # Vite
db:
image: postgres:16-trixie
env_file:
- ../.env
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
langflow:
image: langflowai/langflow:1.12.0
depends_on:
db:
condition: service_healthy
env_file:
- ../.env
environment:
# Static values only; secrets/URLs come from ../.env via env_file
# (compose interpolates ${} from .devcontainer/.env, not the repo-root .env).
LANGFLOW_CONFIG_DIR: /app/langflow
LANGFLOW_HOST: 0.0.0.0
PORT: "7860"
LANGFLOW_AUTO_LOGIN: "false"
LANGFLOW_API_KEY_SOURCE: env
volumes:
- langflow-data:/app/langflow
ports:
- "7860:7860"
healthcheck:
test:
[
"CMD-SHELL",
"python -c \"import urllib.request; urllib.request.urlopen('http://localhost:7860/health', timeout=5)\"",
]
interval: 10s
timeout: 5s
retries: 20
start_period: 60s
lfx:
build:
context: .
dockerfile: Dockerfile.lfx
volumes:
- lfx-data:/flows
ports:
- "6004:6004"
healthcheck:
test:
[
"CMD-SHELL",
"python -c \"import urllib.request; urllib.request.urlopen('http://localhost:6004/health', timeout=5)\"",
]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
volumes:
kirche-einfach-venv:
kirche-einfach-node-modules:
postgres-data:
langflow-data:
lfx-data:

View file

@ -0,0 +1,7 @@
-- Runs once on first init of the postgres-data volume.
-- The main app database ($POSTGRES_DB) is created by the image itself.
-- Langflow (authoring UI) gets its own database on the same instance.
CREATE DATABASE langflow;
-- Isolated database for the pytest suite (migrated via Alembic in conftest).
CREATE DATABASE kirche_einfach_test;

View file

@ -13,6 +13,38 @@ echo "--- Installing Node dependencies ---"
cd /workspaces/kirche-einfach/frontend
npm install
# Compose services (db, langflow, lfx) must be up; fail loudly otherwise.
echo "--- Waiting for compose services ---"
if [ ! -f /workspaces/kirche-einfach/.env ]; then
echo "ERROR: /workspaces/kirche-einfach/.env is missing. Copy .env.example to .env and fill in secrets." >&2
exit 1
fi
python3 - <<'EOF'
import socket
import sys
import time
targets = [("db", 5432), ("langflow", 7860), ("lfx", 6004)]
deadline = time.time() + 180
pending = dict(targets)
while pending and time.time() < deadline:
for host, port in list(pending):
s = socket.socket()
s.settimeout(2)
try:
s.connect((host, port))
print(f"OK {host}:{port}", flush=True)
del pending[(host, port)]
except OSError:
pass
finally:
s.close()
if pending:
time.sleep(2)
if pending:
sys.exit(f"ERROR: compose services not ready: {sorted(pending)}")
EOF
echo ""
echo "=== Setup complete ==="
echo "Forgejo username: $FORGEJO_USERNAME"