diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..d89de3e --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,21 @@ +# Production image for the FastAPI backend. +FROM python:3.12-slim + +ENV PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +WORKDIR /srv/backend +COPY pyproject.toml alembic.ini ./ +COPY app ./app +COPY migrations ./migrations +COPY scripts ./scripts +# No lockfile is committed; dependencies resolve at build time. +RUN uv sync --no-dev + +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"] diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 0000000..6afd1ea --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,122 @@ +# Minimal self-hosted deployment (internal use: no TLS / reverse proxy). +# +# 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 +services: + app: + 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 + ports: + - "8000:8000" + depends_on: + lfx: + condition: service_healthy + langflow: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: + [ + "CMD-SHELL", + "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=5)\"", + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3000:3000" + depends_on: + app: + condition: service_healthy + restart: unless-stopped + + langflow: + image: langflowai/langflow:latest + env_file: + - .env + environment: + LANGFLOW_CONFIG_DIR: /app/langflow + LANGFLOW_HOST: 0.0.0.0 + PORT: "7860" + LANGFLOW_AUTO_LOGIN: "false" + LANGFLOW_API_KEY_SOURCE: env + LANGFLOW_DATABASE_URL: sqlite:////app/langflow/langflow.db + volumes: + - langflow-data:/app/langflow + ports: + - "7860:7860" + labels: + com.centurylinklabs.watchtower.enable: "true" + restart: unless-stopped + healthcheck: + test: + [ + "CMD-SHELL", + "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:7860/health', timeout=5)\"", + ] + interval: 30s + timeout: 5s + retries: 20 + start_period: 60s + + lfx: + build: + context: .devcontainer + dockerfile: Dockerfile.lfx + env_file: + - .env + volumes: + - lfx-data:/flows + ports: + - "6004:6004" + restart: unless-stopped + healthcheck: + test: + [ + "CMD-SHELL", + "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:6004/health', timeout=5)\"", + ] + interval: 30s + timeout: 5s + retries: 12 + start_period: 30s + + watchtower: + image: nickfedor/watchtower:latest + 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" + restart: unless-stopped + +volumes: + appdata: + langflow-data: + lfx-data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..5fbf3ee --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,13 @@ +# Production image for the React UI (build + Vite preview; internal use). +FROM node:22-slim + +WORKDIR /srv/frontend +COPY package.json package-lock.json ./ +RUN npm ci +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"] diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d8d6737..9da1522 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -12,4 +12,14 @@ export default defineConfig({ }, }, }, + preview: { + port: 3000, + proxy: { + "/api": { + // Overridable at container start (prod compose: http://app:8000). + target: process.env.VITE_API_PROXY_TARGET ?? "http://localhost:8000", + changeOrigin: true, + }, + }, + }, });