feat: unify on single repo-root compose file for dev and prod
This commit is contained in:
parent
9acdbaa453
commit
af993bd43f
18 changed files with 253 additions and 229 deletions
|
|
@ -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"]
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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
|
||||
|
|
|
|||
19
.env.example
19
.env.example
|
|
@ -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=
|
||||
22
.forgejo/workflows/backend.yml
Normal file
22
.forgejo/workflows/backend.yml
Normal 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
|
||||
20
.forgejo/workflows/frontend.yml
Normal file
20
.forgejo/workflows/frontend.yml
Normal 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
|
||||
22
.forgejo/workflows/infra.yml
Normal file
22
.forgejo/workflows/infra.yml
Normal 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
|
||||
20
AGENTS.md
20
AGENTS.md
|
|
@ -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.
|
||||
|
|
|
|||
56
README.md
56
README.md
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
15
backend/entrypoint.sh
Normal 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
|
||||
|
|
@ -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:
|
||||
|
|
@ -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
14
frontend/entrypoint.sh
Normal 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
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue