Environment Variables
docsfy uses environment variables for server startup, authentication, storage, default AI behavior, and local development. In normal usage, put the core server settings in a repo-root .env file. The settings model reads that file automatically, and the provided docker-compose.yaml passes it into the container too.
Note: The
docsfyCLI stores server URL and credentials in~/.config/docsfy/config.tomlor CLI flags such as--host,--port,--username, and--password. The variables on this page mainly affect the server process, the container entrypoint, and the frontend development server.
A practical starting point is the shipped .env.example:
# Required: Admin password (minimum 16 characters)
ADMIN_KEY=
# AI provider and model defaults
# (pydantic_settings reads these case-insensitively)
AI_PROVIDER=cursor
AI_MODEL=gpt-5.4-xhigh-fast
AI_CLI_TIMEOUT=60
# Data directory for database and generated docs
DATA_DIR=/data
# Cookie security (set to false for local HTTP development)
SECURE_COOKIES=true
# Development mode: starts Vite dev server on port 5173 alongside FastAPI
# DEV_MODE=true
If you use the included Compose setup, the container reads the same .env file:
env_file:
- .env
environment:
- ADMIN_KEY=${ADMIN_KEY}
Quick Reference
Core Server Settings
| Variable | Default | Required | What it controls |
|---|---|---|---|
ADMIN_KEY |
none | Yes | Built-in admin authentication and the secret used to hash stored user API keys |
AI_PROVIDER |
cursor |
No | Default AI provider when a generation request does not specify one |
AI_MODEL |
gpt-5.4-xhigh-fast |
No | Default AI model when a generation request does not specify one |
AI_CLI_TIMEOUT |
60 |
No | Default timeout value passed to provider CLI calls |
DATA_DIR |
/data |
No | Root directory for the SQLite database and generated documentation artifacts |
SECURE_COOKIES |
true |
No | Whether the browser session cookie uses the Secure flag |
Launcher And Development Settings
| Variable | Default | Used by | What it controls |
|---|---|---|---|
HOST |
127.0.0.1 |
docsfy-server |
Backend bind host when you run the packaged server entrypoint directly |
PORT |
8000 |
docsfy-server |
Backend bind port when you run the packaged server entrypoint directly |
DEBUG |
unset | docsfy-server |
Enables Uvicorn reload when set to true |
DEV_MODE |
unset | entrypoint.sh |
Starts Vite and FastAPI reload together inside the container |
API_TARGET |
http://localhost:8000 |
Vite dev server | Proxy target for /api, /docs, /health, and WebSocket traffic during frontend development |
How docsfy Reads These Values
Most server settings come from src/docsfy/config.py:
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
admin_key: str = ""
ai_provider: str = "cursor"
ai_model: str = "gpt-5.4-xhigh-fast"
ai_cli_timeout: int = Field(default=60, gt=0)
data_dir: str = "/data"
secure_cookies: bool = True # Set to False for local HTTP dev
That means:
- Core server settings are loaded from
.envor the process environment. - Extra keys in
.envdo not break the settings loader because it usesextra="ignore". HOST,PORT,DEBUG,DEV_MODE, andAPI_TARGETare separate. They are read directly by the launcher, the container entrypoint, or Vite instead of this settings model.
flowchart TD
A["`.env` or process environment"] --> B["`Settings` in `src/docsfy/config.py`"]
B --> C["Startup validates `ADMIN_KEY`"]
B --> D["Generation defaults: `AI_PROVIDER`, `AI_MODEL`, `AI_CLI_TIMEOUT`"]
B --> E["Storage root: `DATA_DIR`"]
B --> F["Browser cookie security: `SECURE_COOKIES`"]
G["Shell environment"] --> H["`docsfy-server` launcher"]
H --> I["`HOST`, `PORT`, `DEBUG`"]
J["Container environment"] --> K["`entrypoint.sh`"]
K --> L["`DEV_MODE=true` starts Vite on `5173` and FastAPI reload on `8000`"]
M["Frontend dev shell environment"] --> N["`frontend/vite.config.ts`"]
N --> O["`API_TARGET` proxies `/api`, `/docs`, `/health`, and `/api/ws`"]
ADMIN_KEY
ADMIN_KEY is the one setting you must provide before the server can start. docsfy checks it during application startup and exits if it is missing or too short:
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)
In practice, ADMIN_KEY is used for all of these:
- Browser login as the built-in
adminuser - Bearer authentication for admin API requests
?token=authentication on/api/ws- The HMAC secret for stored user API keys
That last point matters because database-backed users depend on it too. In src/docsfy/storage.py, user API keys are hashed with ADMIN_KEY:
secret = hmac_secret or os.getenv("ADMIN_KEY", "")
if not secret:
msg = "ADMIN_KEY environment variable is required for key hashing"
raise RuntimeError(msg)
return hmac.new(secret.encode(), key.encode(), hashlib.sha256).hexdigest()
Warning: Rotating
ADMIN_KEYinvalidates existing database-backed user API keys. After you change it, those users will need new API keys.Note: The built-in admin username is always
admin. The/api/auth/rotate-keyendpoint does not rotateADMIN_KEY; for that case the API explicitly tells you to change the environment variable itself.
Default AI Settings: AI_PROVIDER, AI_MODEL, And AI_CLI_TIMEOUT
These are server-wide defaults for documentation generation. They are used when a generate request does not provide its own provider, model, or timeout.
The generate API resolves defaults this way in src/docsfy/api/projects.py:
settings = get_settings()
ai_provider = gen_request.ai_provider or settings.ai_provider
ai_model = gen_request.ai_model or settings.ai_model
task = asyncio.create_task(
_run_generation(
repo_url=gen_request.repo_url,
repo_path=gen_request.repo_path,
project_name=project_name,
ai_provider=ai_provider,
ai_model=ai_model,
ai_cli_timeout=gen_request.ai_cli_timeout
or settings.ai_cli_timeout,
The supported provider names in the current codebase are defined in src/docsfy/models.py:
VALID_PROVIDERS = ("claude", "gemini", "cursor")
What each setting does:
AI_PROVIDERsets the default provider for new generations.AI_MODELsets the default model name for that provider.AI_CLI_TIMEOUTsets the default timeout value passed to provider CLI calls.AI_CLI_TIMEOUTmust be greater than0, because the settings model usesField(default=60, gt=0).
A few practical details matter here:
- You can still override provider and model per request through the API or CLI.
docsfyvalidates the provider name againstclaude,gemini, andcursor.- The model is provider-specific; after defaults are resolved,
docsfyonly requires that it not be empty. - When the selected provider is
cursor, generation code adds--trustwhen invoking the CLI.
Tip: Set
AI_PROVIDERandAI_MODELto the combination you use most often. That keeps routine runs simple, while still allowing one-off overrides.Note:
AI_CLI_TIMEOUTaffects the provider CLI calls made by the server. It does not change thedocsfyCLI’s own HTTP client timeout, which is a separate hardcoded30.0insrc/docsfy/cli/client.py.
DATA_DIR
DATA_DIR controls where docsfy stores durable runtime data. By default, that is /data.
From 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"
Project variants are then stored under owner, project, branch, provider, and model directories:
return (
PROJECTS_DIR
/ safe_owner
/ _validate_name(name)
/ branch
/ ai_provider
/ ai_model
)
In practice, the on-disk layout looks like this:
-
DATA_DIR/docsfy.dbStores users, sessions, projects, access grants, and generation metadata. -
DATA_DIR/projects/<owner>/<project>/<branch>/<provider>/<model>/plan.jsonStores the current documentation plan. -
DATA_DIR/projects/<owner>/<project>/<branch>/<provider>/<model>/cache/pages/*.mdStores cached page markdown used for incremental regeneration. -
DATA_DIR/projects/<owner>/<project>/<branch>/<provider>/<model>/site/Stores the rendered static site thatdocsfyserves and downloads.
The included Compose setup is wired to the default path:
volumes:
- ./data:/data
env_file:
- .env
So with the default configuration, your host machine ends up with:
./data/docsfy.db./data/projects/
Warning: If you change
DATA_DIR, also change the container mount target. The provided Compose file assumesDATA_DIR=/datatogether with./data:/data.Tip: The mounted directory must be writable by the runtime user. The provided image is already prepared for
/data, so keeping the default is the simplest container deployment.
SECURE_COOKIES
SECURE_COOKIES controls whether the browser session cookie is marked Secure. It defaults to true, which is the right choice for HTTPS deployments.
From src/docsfy/api/auth.py:
response.set_cookie(
"docsfy_session",
session_token,
httponly=True,
samesite="strict",
secure=settings.secure_cookies,
max_age=SESSION_TTL_SECONDS,
)
This tells you a lot about the browser session behavior:
- The cookie name is
docsfy_session. - It is always
HttpOnly. - It is always
SameSite=Strict. - Its lifetime is fixed in code by
SESSION_TTL_SECONDS = 28800, which is 8 hours. SECURE_COOKIEScontrols only theSecureflag.
The test suite also verifies that the cookie stores an opaque session token, not the raw API key.
This setting matters for more than just page loads. Browser-side API requests use same-origin credentials, and browser WebSocket auth can also use the same docsfy_session cookie.
Warning: If you are running over plain local HTTP, leave
SECURE_COOKIES=falseduring development. Withtrue, the browser will not send the secure cookie back overhttp://, so login can appear not to stick.Tip: Keep
SECURE_COOKIES=trueanywhere the app is served over HTTPS.Note: The session lifetime itself is not configurable by environment variable in the current codebase. Only the
Secureflag is.
HOST, PORT, And DEBUG
These three variables are read directly by the packaged backend launcher in 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)
They only affect runs that use the docsfy-server entrypoint.
What they do:
HOSTchanges the bind address and defaults to127.0.0.1.PORTchanges the bind port and defaults to8000.DEBUG=trueenables Uvicorn reload mode.
A subtle but important detail: these are not part of the Settings model above. They are read directly from the process environment.
Tip: If you launch
docsfy-serverdirectly, setHOST,PORT, andDEBUGin the shell or service manager that starts the process.Note: The provided container entrypoint does not use these variables. It starts Uvicorn explicitly on
0.0.0.0:8000.
DEV_MODE
DEV_MODE is handled by entrypoint.sh, so it is a container startup switch rather than a normal app setting.
From entrypoint.sh:
if [ "$DEV_MODE" = "true" ]; then
echo "DEV_MODE enabled - installing frontend dependencies..."
cd /app/frontend || exit 1
npm ci
echo "Starting Vite dev server on port 5173..."
npm run dev &
VITE_PID=$!
trap 'kill $VITE_PID 2>/dev/null; wait $VITE_PID 2>/dev/null' SIGTERM SIGINT
cd /app
echo "Starting FastAPI with hot reload on port 8000..."
uv run --no-sync uvicorn docsfy.main:app \
--host 0.0.0.0 --port 8000 \
--reload --reload-dir /app/src
else
exec uv run --no-sync uvicorn docsfy.main:app \
--host 0.0.0.0 --port 8000
fi
With DEV_MODE=true inside the container:
- The entrypoint runs
npm ci - Vite starts on port
5173 - FastAPI still runs on port
8000 - The backend uses
--reload
Without DEV_MODE:
- Only FastAPI starts
- The container serves the already-built frontend on port
8000
The sample Compose file already shows the related toggles:
ports:
- "8000:8000"
# Uncomment for development (DEV_MODE=true)
# - "5173:5173"
volumes:
- ./data:/data
# Uncomment for development (hot reload)
# - ./frontend:/app/frontend
environment:
- ADMIN_KEY=${ADMIN_KEY}
# Uncomment for development
# - DEV_MODE=true
Warning:
DEV_MODEis a development convenience, not a production setting.Note: If you run
docsfy-serverdirectly on your host,DEV_MODEdoes nothing becauseentrypoint.shis not involved.
API_TARGET
API_TARGET is only used when you run the frontend dev server with Vite.
From frontend/vite.config.ts:
const API_TARGET = process.env.API_TARGET || 'http://localhost:8000'
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/api': {
target: API_TARGET,
changeOrigin: true,
ws: true,
},
'/docs': {
target: API_TARGET,
changeOrigin: true,
},
'/health': {
target: API_TARGET,
changeOrigin: true,
},
},
},
})
This is what makes the split development workflow work cleanly:
- Open the browser at
http://localhost:5173 - Let Vite proxy
/api,/docs, and/healthto the backend - Let the
/apiproxy carry WebSocket traffic too becausews: trueis enabled
If your backend is not running on http://localhost:8000, this is the variable to change for frontend development.
Tip:
API_TARGETis a frontend-development variable only. It does not affect the built frontend served by FastAPI and it does not change the backend’s own bind address.
What Most Deployments Actually Need
For a normal deployment, the settings that matter most are:
ADMIN_KEYAI_PROVIDERAI_MODELAI_CLI_TIMEOUTDATA_DIRSECURE_COOKIES
For local frontend development, add:
DEV_MODEif you want the container to run Vite for youAPI_TARGETif Vite should proxy to a backend other thanhttp://localhost:8000
For direct host-local backend launches with docsfy-server, add:
HOSTPORTDEBUG
If you set only one thing first, set ADMIN_KEY. That is the one environment variable the server cannot run without.