Deployment Topologies

docsfy supports three practical deployment modes with the same core runtime behavior:

  • Local process (single host, direct Python runtime)
  • Containerized (Docker image + Compose orchestration)
  • OpenShift-style non-root runtime (arbitrary UID, root-group writable paths)

Shared Runtime Contract

Regardless of topology, startup and storage behavior are consistent.

# src/docsfy/config.py
class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )

    admin_key: str = ""  # Required — validated at startup
    ai_provider: str = "claude"
    ai_model: str = "claude-opus-4-6[1m]"  # [1m] = 1 million token context window
    ai_cli_timeout: int = Field(default=60, gt=0)
    log_level: str = "INFO"
    data_dir: str = "/data"
    secure_cookies: bool = True  # Set to False for local HTTP dev
# src/docsfy/main.py
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    settings = get_settings()
    if not settings.admin_key:
        logger.error("ADMIN_KEY environment variable is required")
        raise SystemExit(1)

    if len(settings.admin_key) < 16:
        logger.error("ADMIN_KEY must be at least 16 characters long")
        raise SystemExit(1)

    _generating.clear()
    await init_db(data_dir=settings.data_dir)
    await cleanup_expired_sessions()
    yield
# src/docsfy/storage.py
DB_PATH = Path(os.getenv("DATA_DIR", "/data")) / "docsfy.db"
DATA_DIR = Path(os.getenv("DATA_DIR", "/data"))
PROJECTS_DIR = DATA_DIR / "projects"

# ...
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)

Warning: ADMIN_KEY is mandatory and must be at least 16 characters. The app exits at startup if it is missing or too short.

Warning: The process must be able to write to DATA_DIR (default /data) to create docsfy.db and project artifacts.


Topology 1: Local Process Deployment

Use this mode for development, single-user setups, or tightly controlled internal hosts.

Runtime entry point

# pyproject.toml
[project.scripts]
docsfy = "docsfy.main:run"
# src/docsfy/main.py
def run() -> None:
    import uvicorn

    reload = os.getenv("DEBUG", "").lower() == "true"
    host = os.getenv("HOST", "127.0.0.1")
    port = int(os.getenv("PORT", "8000"))
    uvicorn.run("docsfy.main:app", host=host, port=port, reload=reload)

Configuration pattern

# .env.example
ADMIN_KEY=your-secure-admin-key-here-min-16-chars

AI_PROVIDER=claude
AI_MODEL=claude-opus-4-6[1m]
AI_CLI_TIMEOUT=60

LOG_LEVEL=INFO

# Set to false for local HTTP development
# SECURE_COOKIES=false

Local deployment notes

  • Default bind is 127.0.0.1:8000; set HOST=0.0.0.0 only when you intentionally expose it.
  • SECURE_COOKIES defaults to true; for plain HTTP local testing, set SECURE_COOKIES=false.
  • Persistent state is filesystem-based (DATA_DIR, SQLite file, generated project/site outputs).
  • Generation checks AI CLI availability before work starts (check_ai_cli_available in src/docsfy/main.py), so provider CLIs must be installed and on PATH in local installs.

Tip: Keep local data isolated by setting DATA_DIR to a project-local folder during development.


Topology 2: Containerized Deployment (Docker / Compose)

Use this mode for reproducible packaging and host portability.

Image characteristics

# Dockerfile
FROM python:3.12-slim AS builder
# ...
RUN uv sync --frozen --no-dev

FROM python:3.12-slim
# ...
RUN apt-get update && apt-get install -y --no-install-recommends \
    bash \
    git \
    curl \
    nodejs \
    npm \
    && rm -rf /var/lib/apt/lists/*
# Dockerfile
# Install Claude Code CLI (installs to ~/.local/bin)
RUN /bin/bash -o pipefail -c "curl -fsSL https://claude.ai/install.sh | bash"

# Install Cursor Agent CLI (installs to ~/.local/bin)
RUN /bin/bash -o pipefail -c "curl -fsSL https://cursor.com/install | bash"

# Configure npm for non-root global installs and install Gemini CLI
RUN mkdir -p /home/appuser/.npm-global \
    && npm config set prefix '/home/appuser/.npm-global' \
    && npm install -g @google/gemini-cli
# Dockerfile
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

ENTRYPOINT ["uv", "run", "--no-sync", "uvicorn", "docsfy.main:app", "--host", "0.0.0.0", "--port", "8000"]

Compose topology in repo

# docker-compose.yaml
services:
  docsfy:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    volumes:
      - ./data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Container deployment notes

  • The container always serves on 0.0.0.0:8000 via ENTRYPOINT.
  • /data is the persistence boundary and should be mounted to durable storage.
  • Health is probe-ready through /health and both image-level and Compose-level health checks are defined.
  • Runtime includes AI CLIs plus git, matching generation dependencies.

Note: docker-compose.yaml already maps ./data to /data, which aligns with the default data_dir in app settings.


Topology 3: OpenShift-Style Non-Root Runtime

This image explicitly encodes compatibility with restricted/container-security platforms that run with an arbitrary non-root UID.

# Dockerfile
# OpenShift runs containers as a random UID in the root group (GID 0)
RUN useradd --create-home --shell /bin/bash -g 0 appuser \
    && mkdir -p /data \
    && chown appuser:0 /data \
    && chmod -R g+w /data
# Dockerfile
# Make /app group-writable for OpenShift compatibility
RUN chmod -R g+w /app

# Directories need group write+execute for OpenShift's arbitrary UID (in GID 0)
RUN find /home/appuser -type d -exec chmod g=u {} + \
    && npm cache clean --force 2>/dev/null; \
    rm -rf /home/appuser/.npm/_cacache

USER appuser
ENV PATH="/home/appuser/.local/bin:/home/appuser/.npm-global/bin:${PATH}"
ENV HOME="/home/appuser"
# Dockerfile
# --no-sync prevents uv from attempting to modify the venv at runtime.
# This is required for OpenShift where containers run as an arbitrary UID
ENTRYPOINT ["uv", "run", "--no-sync", "uvicorn", "docsfy.main:app", "--host", "0.0.0.0", "--port", "8000"]

Why these settings matter

  • Arbitrary UID support: group-writable paths (/app, /data, home dirs) allow runtime writes without root.
  • No passwd-entry dependency: HOME=/home/appuser ensures tools can resolve user-home paths even with random UID.
  • Read-only venv safety: uv run --no-sync prevents runtime attempts to mutate .venv, which can fail under restricted permissions.
  • Non-root execution: final runtime user is appuser, not root.

Warning: Do not remove --no-sync from the container startup command in restricted non-root environments; runtime package sync/write attempts can fail.

Warning: Any mounted volume used for /data must permit group write compatible with GID 0 behavior.

Note: This repository does not include Kubernetes/OpenShift manifest files. Platform manifests should preserve the image contract above (non-root, writable /data, unchanged startup semantics).


Health, Auth, and Probe Behavior

# src/docsfy/main.py
class AuthMiddleware(BaseHTTPMiddleware):
    _PUBLIC_PATHS = frozenset({"/login", "/login/", "/health"})
# src/docsfy/main.py
@app.get("/health")
async def health() -> dict[str, str]:
    return {"status": "ok"}
  • /health is intentionally unauthenticated and suitable for liveness/readiness checks.
  • Most other routes are auth-protected by middleware.
  • The code explicitly expects edge-level protections (for example, login rate limiting) to be handled by reverse proxy/ingress when needed.

CI/CD and Verification Inputs in This Repo

No GitHub Actions or GitLab CI pipeline files are present in the repository root structure. Validation is defined through local/pipeline-friendly config:

# tox.toml
[env.unittests]
deps = ["uv"]
commands = [["uv", "run", "--extra", "dev", "pytest", "-n", "auto", "tests"]]
# .pre-commit-config.yaml
ci:
  autofix_prs: false
  autoupdate_commit_msg: "ci: [pre-commit.ci] pre-commit autoupdate"

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
  - repo: https://github.com/PyCQA/flake8
  - repo: https://github.com/Yelp/detect-secrets
  - repo: https://github.com/astral-sh/ruff-pre-commit
  - repo: https://github.com/gitleaks/gitleaks
  - repo: https://github.com/pre-commit/mirrors-mypy

Tip: In external CI/CD systems, treat tox + pre-commit hooks as the minimum gate before publishing container images.