Files
javadandCursor b93ecf1691 Initial commit of the Kamidso API.
Keep source, config, and deploy files. Leave runtime data, uploads, and logs out of git.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 13:43:09 +03:30

12 KiB

Coolify deployment

The API and the web UI are two Git repositories. Each one is a Dockerfile application. MongoDB is a Coolify database resource. Nothing in this layout is a shared production Compose file.

docker-compose.yml in the backend repository is the local API stack (Mongo + API). Coolify should deploy each repository from its own root, not from a monorepo subdirectory.

Checked against the current Coolify docs: Base Directory is the build context, and Dockerfile Location is relative to that directory. The form shows the joined path.

Applications

Frontend — https://kamidso.com

Field Value
Repository frontend repository
Branch main
Build pack / strategy Dockerfile
Base Directory /
Dockerfile Location /Dockerfile
Joined path shown by Coolify /Dockerfile
Ports Exposes 80
Domain https://kamidso.com
Watch Paths **

The frontend repository is the whole build context.

Build-time variables. In Environment Variables set Build time to Available during build and Runtime to Not available in the container. Create a variable only when it has a value. An empty value overrides the Dockerfile default.

Name Value
VITE_API_BASE_URL https://api.kamidso.com
NGINX_CONF nginx.prod.conf

VITE_API_BASE_URL is inlined by Vite. Changing it in the running container does nothing until the next image build. It is declared after npm ci, so changing it does not reinstall npm packages.

nginx.prod.conf serves the SPA only. The browser calls api.kamidso.com itself. That keeps the session cookie on the API host. The local Compose file does not set NGINX_CONF, so it still uses nginx.conf and proxies /api to the backend service.

Optional, only if a mirror is required: NPM_REGISTRY, NODE_IMAGE, NGINX_IMAGE. See Mirrors.

Backend — https://api.kamidso.com

Field Value
Repository backend repository
Branch main
Build pack / strategy Dockerfile
Base Directory /
Dockerfile Location /Dockerfile
Joined path shown by Coolify /Dockerfile
Ports Exposes 5670
Domain https://api.kamidso.com
Watch Paths **

The image command is the one already in the Dockerfile:

python -m kamidso.cli serve --host 0.0.0.0 --port 5670

Do not replace it with a guessed uvicorn line. Leave Ports Mapped empty so 5670 is not published on the host. The proxy reaches the container on the Docker network.

Persistent storage: one volume mounted at /app/data. Engine SQLite and Chroma paths are under that directory (data/meta, data/vectors). Set KAMIDSO_STORAGE_PATH=/app/data.

The image health check is GET /health/live on port 5670, start period 180 seconds. /health/ready reports Mongo and the engine and can stay degraded while those are still coming up, so it is the wrong deploy gate. If Coolify also has a health check, use the same live path and a start period of at least 180 seconds.

Runtime variables. Set Build time to Not available during build. Secrets must not be build arguments.

Name Value
KAMIDSO_ENV production
KAMIDSO_DEVELOPER_MODE false
OTP_MOCK_ENABLED false
KAMIDSO_HOST 0.0.0.0
KAMIDSO_PORT 5670
KAMIDSO_ENGINE_CONFIG configs/dbgpt-proxy-openai.toml
KAMIDSO_STORAGE_PATH /app/data
KAMIDSO_DEFAULT_LOCALE fa-IR
DBGPT_LANG fa
DBGPT_CORS_ALLOWED_ORIGINS https://kamidso.com,https://www.kamidso.com
LLM_MODEL_PROVIDER proxy/openai
EMBEDDING_MODEL_PROVIDER proxy/openai
MONGODB_URI internal URL from the Coolify database resource
KAMIDSO_JWT_SECRET secret, runtime only
KAMIDSO_ADMIN_EMAIL secret, runtime only
KAMIDSO_ADMIN_PASSWORD secret, runtime only
ENGINE_ENCRYPT_KEY secret, runtime only
OPENAI_API_KEY secret, runtime only
OPENAI_API_BASE model endpoint, runtime only
LLM_MODEL_NAME runtime only
EMBEDDING_MODEL_NAME runtime only
EMBEDDING_MODEL_API_URL runtime only
KAVENEGAR_API_KEY secret, runtime only
OPENROUTER_PROXY_URL see Egress

KAMIDSO_ENV=production locks product CORS to https://kamidso.com and https://www.kamidso.com. The engine reads DBGPT_CORS_ALLOWED_ORIGINS separately; leave it as that same pair so it does not fall back to *.

The session cookie is host-only (Domain is not set), HttpOnly, SameSite=Lax, and Secure when KAMIDSO_ENV=production. It is stored for api.kamidso.com. Streaming stays on that host: the SPA does not proxy SSE through nginx.

Optional build-time variables, only when a mirror is required: PIP_INDEX_URL, PIP_TRUSTED_HOST, DEBIAN_MIRROR, DEBIAN_SECURITY_MIRROR, PYTHON_IMAGE. Build time available, runtime not available.

Cache settings on both applications

Configuration → Advanced → Build:

Setting Value
Build cache Use Docker build cache
Build arguments Managed manually in Dockerfile
Source commit availability Runtime only (preserves cache)

Automatic injection writes an ARG line for every build-time variable at the top of the Dockerfile and drops the dependency-layer cache whenever any of those values change. Manual mode keeps the ARG positions in the Dockerfiles.

Ordinary deploys use the normal Deploy button. Force deploy (without cache) rebuilds apt, pip, and npm from scratch. Use it only when a layer is known to be wrong.

On the Coolify server, BuildKit's own garbage collection can still delete this cache. /etc/docker/daemon.json:

{
  "builder": {
    "gc": {
      "enabled": true,
      "defaultKeepStorage": "20GB"
    }
  }
}

Then systemctl restart docker. Pick a size that fits the disk. The backend image's dependency layer is large; a small cap evicts it between deploys.

MongoDB

Create a Coolify database resource. Do not install MongoDB in the backend image, and do not publish its port.

  1. Same server and the same destination network as the backend. The default destination network is coolify. Dockerfile applications join that network, so the database hostname resolves.
  2. Leave the public port unset.
  3. Copy the resource's internal URL into the backend's MONGODB_URI.
  4. The URL contains the user and password. It lives only in Coolify's environment variables.

docker/kamidso/init-app-user.sh is a local init script. Coolify creates the database user itself. The script is not an input to the image.

Egress

OPENROUTER_PROXY_URL is runtime configuration. The Dockerfile does not set HTTP_PROXY on the running container.

Production OpenRouter traffic uses http://egress-gateway:10809 on the external network vira-egress. A Dockerfile application has one destination network. Custom Docker Options cannot add --network; Coolify drops that flag.

Put the egress gateway on the same destination network as the backend and keep OPENROUTER_PROXY_URL=http://egress-gateway:10809. If the gateway stays only on vira-egress, point OPENROUTER_PROXY_URL at an address the backend can already reach. Leave the variable empty only when OpenRouter is reachable directly.

Mirrors

Defaults are Docker Hub, deb.debian.org, https://pypi.org/simple, and https://registry.npmjs.org. Nothing in the Dockerfiles pins an Iranian mirror. Set the variables below on the Coolify application when the production server cannot use the upstream quickly.

These URLs were requested from this network on 2026-10-09. A 200 means the package index or archive responded with real content. A Docker registry 401 on /v2/ is the normal unauthenticated response, not a dead host.

Use Primary What responded Fallback
PyPI https://package-mirror.liara.ir/repository/pypi/simple 200, pip package links https://mirror-pypi.runflare.com/simple
npm https://package-mirror.liara.ir/repository/npm/ 200, react metadata https://mirror-npm.runflare.com
Debian https://linux-mirror.liara.ir/repository/debian 200, bookworm Release leave DEBIAN_MIRROR unset
Debian security https://linux-mirror.liara.ir/repository/debian-security 200, bookworm-security InRelease leave DEBIAN_SECURITY_MIRROR unset
Docker Hub pull-through https://docker-mirror.liara.ir 401 on /v2/ https://docker.arvancloud.ir

Runflare's free mirror quota is limited per IP. If it starts refusing requests, switch that variable back to Liara or to the official index.

Preferred Docker Hub setup is a daemon registry mirror, because the image name stays python:3.12-slim-bookworm and the layer cache key does not change:

{
  "registry-mirrors": ["https://docker-mirror.liara.ir"]
}

If that host fails, replace it with https://docker.arvancloud.ir. If both fail, remove registry-mirrors and pull from Docker Hub. Restart Docker after editing daemon.json. This can sit next to the BuildKit builder.gc block in the same file.

PYTHON_IMAGE, NODE_IMAGE, and NGINX_IMAGE are the fallback when the server cannot set a registry mirror. Setting them changes the cache key. Example: PYTHON_IMAGE=python:3.12-slim-bookworm is already the default; only set the variable to a different name when you must.

Backend apt and pip, when Liara is the chosen mirror:

DEBIAN_MIRROR=https://linux-mirror.liara.ir/repository/debian
DEBIAN_SECURITY_MIRROR=https://linux-mirror.liara.ir/repository/debian-security
PIP_INDEX_URL=https://package-mirror.liara.ir/repository/pypi/simple

PIP_TRUSTED_HOST is unnecessary for these HTTPS indexes. Set it only if pip reports a certificate error, and then only to that host.

Frontend:

NPM_REGISTRY=https://package-mirror.liara.ir/repository/npm/

Alpine packages are already inside node:22-alpine and nginx:1.27-alpine. The frontend Dockerfile does not run apk add. An Alpine mirror would not speed this build. Liara's Alpine mirror did respond (v3.21 APKINDEX 200) if a later change installs Alpine packages.

Dependency install

Backend install input is requirements.txt, copied before the source tree. pyproject.toml is local tooling and is not an install input. The image does not use uv sync. Liara and Docker both install from requirements.txt.

The application is imported from /app. It is not an editable install, so a source change does not reinstall third-party packages.

Frontend install input is package.json and package-lock.json, then npm ci. npm 12.2.0 is installed first because npm 10.9.9 in node:22-alpine crashes while building the tree. Source and VITE_API_BASE_URL come after that layer.

pip and npm download caches use BuildKit cache mounts (RUN --mount=type=cache). Those mounts speed a rebuild of the install layer. They are not a substitute for the layer cache above. The Dockerfiles do not start with # syntax=docker/dockerfile, because that line pulls docker/dockerfile from Docker Hub before any other step. Coolify's BuildKit already accepts cache mounts. A log line that says --mount is unknown means that server's Docker is too old for this Dockerfile.

What a normal deploy should show

Source-only push: npm ci / pip install -r requirements.txt lines are CACHED. The COPY of the source and the Vite build (frontend) or the final COPY . . (backend) run again.

requirements.txt, package.json, or package-lock.json changed: the install layer runs again. The apt layer stays cached unless the base image or the Debian mirror args changed.

No file in the watch path changed: Coolify does not start a deployment. A manual deploy with a warm cache also skips the install when those files are unchanged.