LANMS runs as three containers: Next.js frontend, FastAPI backend, and PostgreSQL. Service images live under frontend/Dockerfile, backend/Dockerfile, and postgres/Dockerfile. The root docker-compose.yml is the main full-stack entry for local use and self-hosting.
There is no single all-in-one root image. Staging on Coolify deploys frontend and backend as separate applications (see Coolify).
- Docker Engine with the Compose plugin (
docker compose version) - A copy of the repository
- Enough free disk for Node and Python image builds
From the repository root:
cp .env.example .envEdit .env before the first build:
- Set
DB_PASSWORD(and keepDB_HOST=dbfor Compose). - Generate an RSA JWT key pair and set
JWT_PUBLIC_KEY/JWT_PRIVATE_KEY(see comments in.env.example). Use literal\nfor PEM newlines; the backend normalises them on load. - Set a strong
OTP_SECRET_KEY. - Keep
PORTAL_URLas the browser URL of the frontend (http://localhost:8080for local Compose). - Keep
NEXT_PUBLIC_CORE_API_URLas a browser-reachable API base (http://localhost:8000/v3for local Compose). Do not use the Docker service hostnamebackendhere; the browser cannot resolve it.
NEXT_PUBLIC_* values are baked into the frontend image at build time. Change them, then rebuild the frontend service.
For local Compose, ENV=local is appropriate. For Coolify staging or production, use staging or production and supply all required backend secrets (JWT keys, OTP_SECRET_KEY, PORTAL_URL).
Local Compose may use POSTGRES_HOST_AUTH_METHOD=trust. On Coolify, staging, or production, set a real database password and do not rely on trust.
From the repository root:
docker compose up --buildDetached mode:
docker compose up --build -d| Service | Default host URL | Container port |
|---|---|---|
| Frontend | http://localhost:8080 | 8080 |
| Backend API | http://localhost:8000 | 8000 |
| PostgreSQL | localhost:5432 | 5432 |
Healthchecks:
- Backend:
GET /v3/system/up - Frontend: root URL on port 8080
- Database:
pg_isready
Stop and remove containers (keeps the Postgres volume):
docker compose downImages do not run Alembic on start. After the stack is healthy:
docker compose exec backend alembic upgrade headOptional bootstrap user (set INITIAL_USER_EMAIL and INITIAL_USER_PASSWORD in .env first):
docker compose exec backend python create_initial_user.pyUse Compose to validate that the stack builds and passes healthchecks. Automated unit and integration tests are not run inside the production backend image (tests/ is excluded from that image by design).
Run backend tests on the host (see backend docs):
cd backend
uv sync --all-groups
uv run pytestCI also runs backend checks against a Postgres service (see .github/workflows/code-backend.yml).
GitHub Actions on pushes to develop trigger Coolify webhooks for backend and frontend separately (.github/workflows/staging-backend.yml, .github/workflows/staging-frontend.yml).
Configure two Coolify applications from this repository:
| App | Base directory | Dockerfile | Port | Notes |
|---|---|---|---|---|
| Backend | backend |
Dockerfile |
8000 | Health path /v3/system/up. Set DB and secret env vars at runtime. |
| Frontend | frontend |
Dockerfile |
8080 | Set build args NEXT_PUBLIC_ENV and NEXT_PUBLIC_CORE_API_URL to the public API URL (for example https://api.example.com/v3). |
Use a Coolify-managed PostgreSQL (or external DB) and point the backend at it with DB_*. Run migrations as a one-off after deploy (alembic upgrade head in the backend container or an equivalent Coolify execute command).
You can also deploy the root docker-compose.yml as a Coolify Compose resource for full-stack self-hosting. Set the same root .env values, use a browser-reachable NEXT_PUBLIC_CORE_API_URL, and prefer password auth for Postgres instead of trust.
- Private Docker DNS in the frontend build:
NEXT_PUBLIC_CORE_API_URL=http://backend:8000/v3works only inside the Compose network, not in the user’s browser. Uselocalhostlocally or your public API hostname in staging/production. - Missing secrets in live envs:
staging/productionrequireJWT_PUBLIC_KEY,JWT_PRIVATE_KEY,OTP_SECRET_KEY, andPORTAL_URL. - Stale frontend after env changes: rebuild the frontend image after changing
NEXT_PUBLIC_*. - Port differences outside Docker: Compose uses frontend
8080and backend8000. Local non-Docker setups may use other ports (see frontend and backend docs).
To build a single service without Compose:
docker build -t lanms-backend ./backend
docker build -t lanms-frontend \
--build-arg NEXT_PUBLIC_ENV=local \
--build-arg NEXT_PUBLIC_CORE_API_URL=http://localhost:8000/v3 \
./frontend
docker build -t lanms-postgres ./postgres