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_KEYis 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 createdocsfy.dband 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; setHOST=0.0.0.0only when you intentionally expose it. SECURE_COOKIESdefaults totrue; for plain HTTP local testing, setSECURE_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_availableinsrc/docsfy/main.py), so provider CLIs must be installed and onPATHin local installs.
Tip: Keep local data isolated by setting
DATA_DIRto 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:8000viaENTRYPOINT. /datais the persistence boundary and should be mounted to durable storage.- Health is probe-ready through
/healthand both image-level and Compose-level health checks are defined. - Runtime includes AI CLIs plus
git, matching generation dependencies.
Note:
docker-compose.yamlalready maps./datato/data, which aligns with the defaultdata_dirin 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/appuserensures tools can resolve user-home paths even with random UID. - Read-only venv safety:
uv run --no-syncprevents 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-syncfrom the container startup command in restricted non-root environments; runtime package sync/write attempts can fail.Warning: Any mounted volume used for
/datamust permit group write compatible with GID0behavior.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"}
/healthis 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.