feat: unify on single repo-root compose file for dev and prod
Some checks failed
/ test (push) Has been cancelled
/ build (push) Has been cancelled
/ validate (push) Has been cancelled

This commit is contained in:
Jonas Miederer 2026-09-07 19:46:17 +00:00
parent 9acdbaa453
commit af993bd43f
18 changed files with 253 additions and 229 deletions

View file

@ -1,48 +0,0 @@
FROM mcr.microsoft.com/devcontainers/base:debian
ARG FORGEJO_USERNAME
ENV FORGEJO_USERNAME=${FORGEJO_USERNAME}
# SSH (missing from base image)
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssh-client \
&& rm -rf /var/lib/apt/lists/*
# Node.js 22 + npm (npm tree copied; bin symlinks recreated - COPY dereferences them)
COPY --from=node:22-slim /usr/local/bin/node /usr/local/bin/
COPY --from=node:22-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
# uv (from distroless)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Python 3.12 (system-shared location; default /root is unreadable for vscode)
ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python
RUN uv python install 3.12 \
&& ln -s /opt/uv/python/cpython-3.12-linux-x86_64-gnu/bin/python3.12 /usr/local/bin/python3.12 \
&& ln -s python3.12 /usr/local/bin/python3 \
&& ln -s python3 /usr/local/bin/python
# Trust the workspace bind mount (source on Windows FS -> uid mismatch)
RUN git config --system --add safe.directory /workspaces/kirche-einfach
# Pre-create named-volume targets (volumes inherit image content on first mount).
# The backend venv is created as `vscode` so VS Code's Python extension finds an
# interpreter immediately on first attach, before postCreateCommand runs `uv sync`.
USER vscode
RUN mkdir -p /workspaces/kirche-einfach/backend \
&& uv venv /workspaces/kirche-einfach/backend/.venv \
&& mkdir -p /workspaces/kirche-einfach/frontend/node_modules
USER root
# opencode (as non-root user)
USER vscode
RUN curl -fsSL https://opencode.ai/install | bash
USER root
RUN ln -s /home/vscode/.opencode/bin/opencode /usr/local/bin/opencode
# Fix ownership (home + volume targets so vscode user can write)
RUN chown -R vscode:vscode /home/vscode /workspaces
CMD ["sleep", "infinity"]

View file

@ -1,17 +1,22 @@
{
"name": "kirche-einfach",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"runServices": ["db", "langflow", "lfx"],
"mounts": [
"source=${localEnv:USERPROFILE}/.ssh,target=/mnt/host-ssh,type=bind,readonly"
],
"dockerComposeFile": ["../docker-compose.yml"],
"service": "api",
"runServices": ["api", "frontend", "langflow", "lfx"],
"workspaceFolder": "/srv/kirche-einfach",
"remoteUser": "vscode",
"containerEnv": {
"APP_ENV": "dev",
"FORGEJO_USERNAME": "jonas"
},
"mounts": [
"source=${localEnv:USERPROFILE}/.ssh,target=/mnt/host-ssh,type=bind,readonly",
"source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind"
],
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"overrideCommand": false,
"workspaceFolder": "/workspaces/kirche-einfach",
"postCreateCommand": "sudo -E bash .devcontainer/setup.sh",
"postStartCommand": "bash .devcontainer/postStart.sh",
"customizations": {
@ -24,7 +29,7 @@
"esbenp.prettier-vscode"
],
"settings": {
"python.defaultInterpreterPath": "/workspaces/kirche-einfach/backend/.venv/bin/python"
"python.defaultInterpreterPath": "/srv/kirche-einfach/backend/.venv/bin/python"
}
}
}

View file

@ -1,92 +0,0 @@
# 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
langflow:
image: langflowai/langflow:latest
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
# SQLite file inside the langflow-data volume (absolute path required
# by Langflow); overrides any postgres URL in ../.env.
LANGFLOW_DATABASE_URL: sqlite:////app/langflow/langflow.db
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
env_file:
- ../.env
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
# Optional nightly auto-updates of :latest images. Not part of the default
# dev startup (no container updates needed while iterating).
watchtower:
image: nickfedor/watchtower:latest
profiles: ["updates"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_SCHEDULE: "0 0 4 * * *"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_ROLLING_RESTART: "true"
labels:
com.centurylinklabs.watchtower.enable: "true"
volumes:
kirche-einfach-venv:
kirche-einfach-node-modules:
langflow-data:
lfx-data:

View file

@ -1,22 +1,30 @@
#!/bin/bash
# Dev-only setup (runs via postCreateCommand in the attached `api` container).
# Production needs none of this: images carry their dependencies and the
# api entrypoint migrates on every start.
set -e
REPO=/srv/kirche-einfach
echo "=== Setting up kirche-einfach ==="
# Backend
# Backend deps into the venv volume (setup runs via sudo; handed to vscode below).
echo "--- Installing Python dependencies ---"
cd /workspaces/kirche-einfach/backend
cd "$REPO/backend"
uv sync
# Frontend
echo "--- Installing Node dependencies ---"
cd /workspaces/kirche-einfach/frontend
npm install
# opencode CLI (dev tooling; intentionally not baked into the image).
if [ ! -x /home/vscode/.opencode/bin/opencode ]; then
echo "--- Installing opencode ---"
su vscode -c "curl -fsSL https://opencode.ai/install | bash"
ln -sf /home/vscode/.opencode/bin/opencode /usr/local/bin/opencode
fi
# Compose services (langflow, lfx) must be up; fail loudly otherwise.
# (api/frontend are starting alongside this script.)
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
if [ ! -f "$REPO/.env" ]; then
echo "ERROR: $REPO/.env is missing. Copy .env.example to .env and fill in secrets." >&2
exit 1
fi
python3 - <<'EOF'
@ -45,19 +53,17 @@ if pending:
sys.exit(f"ERROR: compose services not ready: {sorted(pending)}")
EOF
# Migrate the SQLite schema and reconcile backend/flows into the DB + LFX.
echo "--- Migrating database ---"
cd /workspaces/kirche-einfach/backend
uv run alembic upgrade head
# Reconcile backend/flows into the DB + LFX (schema is migrated by the api
# entrypoint on every start).
echo "--- Syncing canonical flows ---"
cd "$REPO/backend"
uv run python scripts/sync_flows.py --sync || echo "WARN: flow sync failed (check backend/flows and LFX)"
echo ""
echo "=== Setup complete ==="
echo "Forgejo username: $FORGEJO_USERNAME"
# Hand dirs back to vscode (this script runs via sudo; uv/npm create caches as root)
chown -R vscode:vscode /workspaces/kirche-einfach/backend/.venv \
/workspaces/kirche-einfach/frontend/node_modules \
# Hand dirs back to vscode (this script runs via sudo; uv creates caches as root)
chown -R vscode:vscode "$REPO/backend/.venv" \
"$REPO/frontend/node_modules" \
/home/vscode/.cache 2>/dev/null || true

View file

@ -1,16 +1,10 @@
# Copy to `.env` and fill in values. `.env` is gitignored.
# It holds only ubiquitous variables (identical value in dev and prod).
# Dev-only variables travel via devcontainer.json `containerEnv`
# (e.g. APP_ENV=dev); paths resolve from the repo root in code.
FORGEJO_TOKEN=
# --- app database (SQLite, default if unset) ---
# Override only if you want a different engine/URL.
# DATABASE_URL=sqlite:///backend/data/kirche.db
# --- canonical flow definitions ---
# FLOWS_DIR=backend/flows
# Auto-reconcile backend/flows into DB + LFX while the API runs.
# FLOWS_WATCH_ENABLED=true
# --- langflow (authoring, dev only) ---
# --- langflow (authoring UI) ---
LANGFLOW_URL=http://langflow:7860
LANGFLOW_SUPERUSER=langflow
LANGFLOW_SUPERUSER_PASSWORD=
@ -24,7 +18,10 @@ LFX_URL=http://lfx:6004
LFX_API_KEY=lfx-
# --- API self-reference for flow callbacks (must be reachable FROM lfx) ---
CALLBACK_BASE_URL=http://app:8000
CALLBACK_BASE_URL=http://api:8000
# --- frontend mode: dev server with HMR (prod .env must set `preview`) ---
FRONTEND_MODE=dev
# --- LLM (optional; tests mock the provider without it) ---
# LLM_API_KEY=

View file

@ -0,0 +1,22 @@
# Backend test suite. Runs the full pytest suite on a throwaway SQLite file.
# Prerequisites on the Forgejo instance (not verifiable from the repo):
# - a Runner with the `docker` label online (repo Settings -> Actions -> Runners)
# - action resolution for actions/checkout (github proxy or code.forgejo.org mirror)
on:
push:
branches: [main]
pull_request:
paths:
- "backend/**"
- "docker-compose.yml"
- ".forgejo/workflows/backend.yml"
jobs:
test:
runs-on: docker
container:
image: ghcr.io/astral-sh/uv:python3.12-bookworm
steps:
- uses: actions/checkout@v4
- run: cd backend && uv sync
- run: cd backend && uv run pytest

View file

@ -0,0 +1,20 @@
# Frontend typecheck + build. Same prerequisites as backend.yml (Runner
# with the `docker` label, action resolution on the Forgejo instance).
on:
push:
branches: [main]
pull_request:
paths:
- "frontend/**"
- "docker-compose.yml"
- ".forgejo/workflows/frontend.yml"
jobs:
build:
runs-on: docker
container:
image: node:22-slim
steps:
- uses: actions/checkout@v4
- run: cd frontend && npm ci
- run: cd frontend && npm run build

View file

@ -0,0 +1,22 @@
# Validates the single compose file parses (needs only the CLI, no daemon).
# Same Runner prerequisites as backend.yml.
on:
push:
branches: [main]
pull_request:
paths:
- "docker-compose.yml"
- ".env.example"
- "backend/Dockerfile"
- "frontend/Dockerfile"
- "Dockerfile.lfx"
- ".forgejo/workflows/infra.yml"
jobs:
validate:
runs-on: docker
container:
image: docker:24
steps:
- uses: actions/checkout@v4
- run: docker compose -f docker-compose.yml config --quiet

View file

@ -13,6 +13,26 @@ Backend (run from `backend/`):
Frontend (run from `frontend/`): `npm install`, `npm run dev`, `npm run build` (typecheck + build).
## Multi-container ops
One `docker-compose.yml` at the repo root for dev and prod. Dev specifics
live in `.devcontainer/devcontainer.json` only — never add dev-only services,
profiles, or override files. Containers: `api` (FastAPI :8000, attached),
`frontend` (Vite :3000), `langflow` (:7860), `lfx` (:6004), `watchtower`
(prod only, not in `runServices`).
- Backend suite (attached container, from `backend/`): `uv run pytest`.
- Frontend build (node lives in `frontend`, reach it via exec from the repo
root): `docker compose exec frontend npm run build`. Fallback without a
docker socket: VS Code "Attach to Running Container" → frontend.
- Logs/restart: `docker compose logs -f <service>`, `docker compose restart api`.
- If `exec` misses the project, find it via `docker compose ls`.
- CI runs on Forgejo (`.forgejo/workflows/`): backend pytest, frontend
build, compose validation. Needs a Runner with the `docker` label online.
- Prod: own `.env` with secrets + `FRONTEND_MODE=preview`; code updates via
`git pull` + restart, image rebuilds only for dependency changes
(`docker compose up -d --build`).
## Definition of done
- New behavior has tests; `uv run pytest` is green before reporting completion.

View file

@ -27,6 +27,10 @@ needs judgment. Every step is documented and traceable.
## Infrastructure
One `docker-compose.yml` at the repo root runs the whole stack — identical
services in dev and prod. Dev-only needs (editor, SSH keys, autoreload,
docker CLI) come from `.devcontainer/devcontainer.json`.
```mermaid
flowchart LR
subgraph Repo["Git repo — source of truth"]
@ -34,11 +38,11 @@ flowchart LR
end
subgraph Stack["Compose stack (dev + prod)"]
UI["frontend — React UI (3000)"]
API["app — FastAPI (8000)<br/>+ embedded flow-watcher"]
API["api — FastAPI (8000)<br/>+ embedded flow-watcher"]
DB[("app.db — SQLite file")]
LF["langflow — authoring UI (7860)<br/>langflow.db — SQLite file"]
LFX["lfx — flow runtime (6004)"]
WT["watchtower — nightly image updates"]
WT["watchtower — nightly image updates (prod)"]
end
UI --> API
FLOWS -->|watch + sync| API
@ -51,8 +55,8 @@ flowchart LR
| Service | Purpose | URL |
| -------------- | ------------------------------------ | -------------------------- |
| React frontend | Web UI (Vite) | http://localhost:3000 |
| FastAPI backend| API + flow orchestration | http://localhost:8000 |
| React frontend | Web UI (Vite, HMR in dev) | http://localhost:3000 |
| FastAPI backend| API + flow orchestration (reload in dev) | http://localhost:8000 |
| API docs | Swagger UI | http://localhost:8000/api/docs |
| Langflow | Visual flow authoring | http://localhost:7860 |
| LFX | Flow runtime (internal, no direct UI)| localhost:6004 |
@ -63,23 +67,22 @@ Langflow itself runs on SQLite by default — so the whole stack works without
a database server. Postgres can return later by changing `DATABASE_URL`
(migrations stay portable).
`.env` holds only ubiquitous variables (identical value in dev and prod:
secrets, service URLs). Dev-only variables travel via `devcontainer.json`
(`APP_ENV=dev`); paths resolve from the repo root in code.
## Getting Started
1. Copy `.env.example` to `.env` and fill in the secrets.
2. Open the repo in VS Code → "Reopen in Container". Setup installs
dependencies, migrates the database, and syncs `backend/flows/`.
3. Run the backend from `backend/`: `uv run uvicorn app.main:app --port 8000`.
Run the frontend from `frontend/`: `npm run dev`.
Production (self-hosted, internal use — no TLS/reverse proxy included):
```sh
docker compose -f compose.prod.yml up -d --build
```
dependencies and syncs `backend/flows/`; the entrypoints migrate and
serve automatically.
3. The stack is already running: UI at http://localhost:3000, API docs at
http://localhost:8000/api/docs.
## Development
Backend (from `backend/`):
Backend (attached `api` container, from `backend/`):
- `uv run pytest` — full test suite (throwaway SQLite file, no external DB).
- `uv run alembic upgrade head` — migrate the dev database.
@ -92,12 +95,33 @@ Backend (from `backend/`):
flows (writes test fixtures and `backend/flows/`).
- `uv run python scripts/smoke_e2e.py` — manual end-to-end smoke.
Frontend (from `frontend/`): `npm install`, `npm run dev`, `npm run build`.
Frontend (node lives in the `frontend` container):
- `docker compose exec frontend npm run dev` — HMR dev server (already
running via compose; this is for a second instance or flags).
- `docker compose exec frontend npm run build` — typecheck + build.
- Without a docker socket: VS Code "Attach to Running Container" → frontend.
Flow workflow: design in Langflow → export into `backend/flows/<name>.json`
(`--pull`, or UI download) → commit → the watcher deploys it automatically.
Deleting a file removes the flow including its runs (git keeps the JSON).
## Production (self-hosted, internal use — no TLS/reverse proxy included)
1. Check out the repo on the host, create `.env` from `.env.example` with
real secrets and `FRONTEND_MODE=preview`.
2. `docker compose up -d --build`.
3. Code updates via `git pull` + `docker compose restart api frontend`
(the checkout is bind-mounted); image rebuilds only for dependency
changes (`docker compose up -d --build`).
Container updates: registry images (`langflowai/langflow:latest`)
auto-update nightly via Watchtower (`nickfedor/watchtower`). Locally built
images (`app`, `lfx`, `frontend`) update with `docker compose build --pull`.
images (`api`, `lfx`, `frontend`) update with `docker compose build --pull`.
## Continuous integration
Workflows in `.forgejo/workflows/` run on Forgejo: backend pytest, frontend
build, compose validation. They need a Runner with the `docker` label online
(repo Settings → Actions → Runners) and action resolution for
`actions/checkout` (github proxy or `code.forgejo.org` mirror).

View file

@ -1,4 +1,6 @@
# Production image for the FastAPI backend.
# Backend image for dev and prod (single image, full dependencies).
# Dev specifics (APP_ENV=dev for autoreload, opencode, SSH keys) arrive via
# devcontainer.json / setup.sh — never via this file.
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 \
@ -6,16 +8,24 @@ ENV PYTHONUNBUFFERED=1 \
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /srv/backend
COPY pyproject.toml alembic.ini ./
# Foundational, inert in prod: non-root user for the editor, sudo for setup,
# openssh-client for the devcontainer post-start hook.
RUN apt-get update \
&& apt-get install -y --no-install-recommends sudo openssh-client \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -m -s /bin/bash vscode \
&& echo "vscode ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/vscode
WORKDIR /srv/kirche-einfach/backend
COPY pyproject.toml alembic.ini entrypoint.sh ./
COPY app ./app
COPY migrations ./migrations
COPY scripts ./scripts
# No lockfile is committed; dependencies resolve at build time.
RUN uv sync --no-dev
# Full groups (incl. dev): pytest and friends must work in dev with zero steps.
RUN uv sync && chmod +x entrypoint.sh
ENV FLOWS_WATCH_ENABLED=true
EXPOSE 8000
# Migrate on start, then serve (watcher reconciles backend/flows automatically).
CMD ["sh", "-c", ".venv/bin/alembic upgrade head && exec .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000"]
ENTRYPOINT ["./entrypoint.sh"]

View file

@ -27,7 +27,7 @@ class Settings(BaseSettings):
lfx_api_key: str = ""
llm_api_key: str = ""
# Base URL flows use for callbacks into this API. Must be reachable
# FROM the LFX process (compose: http://app:8000).
# FROM the LFX process (compose: http://api:8000).
callback_base_url: str = "http://localhost:8000"

15
backend/entrypoint.sh Normal file
View file

@ -0,0 +1,15 @@
#!/bin/sh
# Backend entrypoint (dev + prod): migrate, then serve.
# APP_ENV=dev (set via devcontainer.json containerEnv, attached container
# only) enables autoreload. Anything else runs the plain production server.
set -e
cd /srv/kirche-einfach/backend
.venv/bin/alembic upgrade head
if [ "${APP_ENV:-prod}" = "dev" ]; then
exec .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
else
exec .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
fi

View file

@ -1,29 +1,23 @@
# Minimal self-hosted deployment (internal use: no TLS / reverse proxy).
# The single compose file for dev and prod.
#
# Flows are the bind-mounted ./backend/flows directory (source of truth);
# the embedded API watcher reconciles them into the DB + LFX automatically.
# Registry images (:latest) auto-update nightly via watchtower; locally
# built images (app, lfx, frontend) update via `docker compose build --pull`.
#
# Usage: docker compose -f compose.prod.yml up -d --build
# Dev: VS Code attaches to `api` (see .devcontainer/devcontainer.json) and
# starts `runServices` (everything but watchtower). Dev specifics
# (autoreload, editor, SSH keys, docker CLI) come from devcontainer.json.
# Prod: `docker compose up -d --build` (own .env with secrets +
# FRONTEND_MODE=preview). Code updates via `git pull` + restart; image
# rebuilds only for dependency changes.
services:
app:
api:
build:
context: ./backend
dockerfile: Dockerfile
env_file:
- .env
environment:
DATABASE_URL: sqlite:////srv/backend/data/kirche.db
FLOWS_DIR: /srv/backend/flows
LFX_URL: http://lfx:6004
LANGFLOW_URL: http://langflow:7860
CALLBACK_BASE_URL: http://app:8000
volumes:
- ./backend/flows:/srv/backend/flows
- appdata:/srv/backend/data
- .:/srv/kirche-einfach
- kirche-venv:/srv/kirche-einfach/backend/.venv
ports:
- "8000:8000"
- "8000:8000" # FastAPI
depends_on:
lfx:
condition: service_healthy
@ -45,10 +39,18 @@ services:
build:
context: ./frontend
dockerfile: Dockerfile
env_file:
- .env
environment:
# Dev default (HMR); prod .env sets `preview` (build + serve bundle).
FRONTEND_MODE: ${FRONTEND_MODE:-dev}
volumes:
- .:/srv/kirche-einfach
- kirche-node-modules:/srv/kirche-einfach/frontend/node_modules
ports:
- "3000:3000"
- "3000:3000" # Vite
depends_on:
app:
api:
condition: service_healthy
restart: unless-stopped
@ -57,11 +59,14 @@ services:
env_file:
- .env
environment:
# Static values only; secrets/URLs come from .env via env_file.
LANGFLOW_CONFIG_DIR: /app/langflow
LANGFLOW_HOST: 0.0.0.0
PORT: "7860"
LANGFLOW_AUTO_LOGIN: "false"
LANGFLOW_API_KEY_SOURCE: env
# SQLite file inside the langflow-data volume (absolute path required
# by Langflow); overrides any postgres URL that may linger in .env.
LANGFLOW_DATABASE_URL: sqlite:////app/langflow/langflow.db
volumes:
- langflow-data:/app/langflow
@ -83,7 +88,7 @@ services:
lfx:
build:
context: .devcontainer
context: .
dockerfile: Dockerfile.lfx
env_file:
- .env
@ -103,6 +108,8 @@ services:
retries: 12
start_period: 30s
# Nightly auto-updates of :latest images (prod). Never started in dev
# (not in devcontainer.json runServices; also needs a docker socket).
watchtower:
image: nickfedor/watchtower:latest
volumes:
@ -117,6 +124,7 @@ services:
restart: unless-stopped
volumes:
appdata:
kirche-venv:
kirche-node-modules:
langflow-data:
lfx-data:

View file

@ -1,13 +1,13 @@
# Production image for the React UI (build + Vite preview; internal use).
# Frontend image for dev and prod (single image).
# Dev specifics arrive via devcontainer.json / compose env — never via this
# file. DevDeps are included: `npm run dev` must work with zero steps.
FROM node:22-slim
WORKDIR /srv/frontend
COPY package.json package-lock.json ./
RUN npm ci
WORKDIR /srv/kirche-einfach/frontend
COPY package.json package-lock.json entrypoint.sh ./
RUN npm ci && chmod +x entrypoint.sh
COPY . ./
RUN npm run build
ENV VITE_API_PROXY_TARGET=http://app:8000
EXPOSE 3000
CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "3000"]
ENTRYPOINT ["./entrypoint.sh"]

14
frontend/entrypoint.sh Normal file
View file

@ -0,0 +1,14 @@
#!/bin/sh
# Frontend entrypoint (dev + prod).
# FRONTEND_MODE=dev (compose default) runs the HMR dev server.
# FRONTEND_MODE=preview (prod .env) builds once and serves the bundle.
set -e
cd /srv/kirche-einfach/frontend
if [ "${FRONTEND_MODE:-dev}" = "dev" ]; then
exec npm run dev -- --host 0.0.0.0 --port 3000
else
npm run build
exec npm run preview -- --host 0.0.0.0 --port 3000
fi

View file

@ -1,13 +1,15 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// Both dev server and preview proxy /api to the backend service.
// `api` is the compose service name, identical in dev and prod.
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:8000",
target: "http://api:8000",
changeOrigin: true,
},
},
@ -16,8 +18,7 @@ export default defineConfig({
port: 3000,
proxy: {
"/api": {
// Overridable at container start (prod compose: http://app:8000).
target: process.env.VITE_API_PROXY_TARGET ?? "http://localhost:8000",
target: "http://api:8000",
changeOrigin: true,
},
},