Keep source, config, and deploy files. Leave runtime data, uploads, and logs out of git. Co-authored-by: Cursor <cursoragent@cursor.com>
212 lines
12 KiB
Markdown
212 lines
12 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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.
|