ae67bff5a3
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
72 lines
4.1 KiB
Docker
72 lines
4.1 KiB
Docker
# Forge API - FastAPI on the MIT LangChain/LangGraph stack.
|
|
FROM python:3.12-slim AS base
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
# The default local (fastembed) embedder's model is baked into the image at this path
|
|
# (see the pre-download step below); the app reads the same dir at runtime, so ingestion
|
|
# works fully offline with no first-run download. Outside the /app/.data volume, so it's
|
|
# part of the image, not the persisted volume.
|
|
FORGE_FASTEMBED_CACHE_DIR=/app/.fastembed-cache
|
|
|
|
# Build deps for psycopg/cryptography wheels (kept minimal; most ship manylinux wheels).
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
build-essential curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Build context is the REPO ROOT (see docker-compose.yml: context: . / dockerfile:
|
|
# apps/api/Dockerfile) so we can COPY packages/schemas, which lives above apps/api.
|
|
#
|
|
# Third-party deps install in a layer keyed only on pyproject.toml + a package stub, so a
|
|
# pure forge/ source edit does NOT invalidate this (expensive) layer. The editable install
|
|
# just links /app/forge onto sys.path, so overwriting the stub with the real tree afterwards
|
|
# needs no reinstall. The BuildKit pip cache mount reuses already-downloaded wheels instead
|
|
# of re-hitting PyPI even when the layer does re-run (e.g. a pyproject.toml change), so a
|
|
# source edit can never turn into a PyPI round-trip / offline build failure.
|
|
COPY apps/api/pyproject.toml apps/api/README.md ./
|
|
RUN mkdir -p forge && touch forge/__init__.py
|
|
# Production extras: model providers, vectors, knowledge, MCP, workers (Redis/arq), Postgres.
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
pip install --upgrade pip && \
|
|
pip install -e ".[providers,vectors,knowledge,mcp,workers,postgres]"
|
|
|
|
# Real source over the stub (this layer changes on every edit, but it is AFTER the install).
|
|
COPY apps/api/forge ./forge
|
|
COPY apps/api/alembic.ini ./alembic.ini
|
|
COPY apps/api/migrations ./migrations
|
|
# Shared JSON Schemas live at the repo root, outside the old ./apps/api context. Bake them at
|
|
# /app/packages/schemas so config.py's schemas_dir default (/app/packages/schemas) resolves.
|
|
COPY packages/schemas ./packages/schemas
|
|
|
|
# Pre-download the default local embedder (fastembed) model into the image so the container
|
|
# ships self-contained: no ~130MB HuggingFace fetch on the first ingest, and no runtime egress
|
|
# to huggingface.co (which a locked-down deploy may block). Keep this model id in sync with
|
|
# forge.knowledge.embeddings._DEFAULT_FASTEMBED. Runs before the chown so the forge user owns it.
|
|
RUN python -c "import os; from fastembed import TextEmbedding; TextEmbedding(model_name='BAAI/bge-small-en-v1.5', cache_dir=os.environ['FORGE_FASTEMBED_CACHE_DIR'])"
|
|
|
|
# Run as a non-root user. Pre-create /app/.data (holds the Fernet master.key + Chroma store)
|
|
# owned by forge: an empty named volume mounted there copies the image mountpoint's ownership on
|
|
# first use, so this keeps the volume forge-writable. Without it the volume would mount root-owned
|
|
# and the non-root process could not write the master key (PermissionError at startup).
|
|
RUN useradd --create-home --uid 10001 forge \
|
|
&& mkdir -p /app/.data \
|
|
&& chown -R forge:forge /app
|
|
USER forge
|
|
|
|
EXPOSE 8000
|
|
|
|
# Container healthcheck hits the readiness probe (DB + checkpointer).
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD curl -fsS http://localhost:8000/readyz || exit 1
|
|
|
|
# Apply migrations, then serve. (Schema also self-bootstraps via create_all, but on managed
|
|
# Postgres `alembic upgrade head` is the controlled path.)
|
|
# No uvicorn --proxy-headers/--forwarded-allow-ips: the app derives the client IP itself and
|
|
# trusts X-Forwarded-For only from FORGE_TRUSTED_PROXIES (default none -> the real socket peer).
|
|
# Letting uvicorn rewrite the peer from XFF for ANY client (as --forwarded-allow-ips='*' did)
|
|
# lets clients spoof their IP to evade per-IP rate limits / poison audit logs. Behind a real
|
|
# proxy, set FORGE_TRUSTED_PROXIES to the proxy IP(s).
|
|
CMD ["sh", "-c", "alembic upgrade head && uvicorn forge.main:app --host 0.0.0.0 --port 8000"]
|