Skip to content

Services

Services are the core of your lokl configuration. Each service represents a process or container that makes up your development environment.

For local processes:

services:
frontend:
command: pnpm dev
path: apps/frontend
port: 5173
FieldTypeDescription
commandstring or listShell command (string → sh -c) or exec args (list). Allowed with image to override the image’s default command.
pathstringWorking directory (relative to config)
portintPort the service listens on
envmapEnvironment variables
env_filelistPaths to .env files to load
depends_onlistServices to start first
autostartboolStart automatically (default: true)
restartstringRestart policy: no, always, on-failure
proxy_onlyboolRegister the subdomain route without starting a process or container. Requires port and subdomain. See Proxy-only services.

For Docker containers:

services:
db:
image: postgres:16
ports:
- "5432:5432"
port: 5432
subdomain: db
env:
POSTGRES_PASSWORD: secret
volumes:
- ./data:/var/lib/postgresql/data
FieldTypeDescription
imagestringDocker image
commandstring or listOverride the image’s default CMD. String → sh -c inside container; list → direct exec.
portslistPort mappings (host:container)
portintHost port for proxy routing and health checks
envmapEnvironment variables
env_filelistPaths to .env files to load
volumeslistVolume mounts. host:container for bind mounts, name:container for named volumes, bare /container/path for anonymous volumes (mask a dir from an outer bind mount).
subdomainstring or list of stringsSubdomain for proxy routing. A list may mix exact names and wildcards like "*.sellify.shop" for multi-tenant apps (macOS only for now).
depends_onlistServices to start first
healthobjectHealth check configuration (see below)
autostartboolStart automatically (default: true)
restartstringRestart policy: no, always, on-failure
proxy_onlyboolRegister the subdomain route without starting a process or container. Requires port and subdomain. See Proxy-only services.

Control startup order with depends_on:

services:
api:
command: pnpm dev
depends_on:
- db
- redis
db:
image: postgres:16
redis:
image: redis:7

Monitor HTTP services:

services:
api:
command: pnpm dev
port: 3000
health:
path: /health
interval: 10s
timeout: 5s
retries: 3

command is allowed alongside image. It overrides the image’s default CMD while preserving ENTRYPOINT — matching docker run image cmd... and Docker Compose’s command: field.

services:
api:
image: node:20
command: "npm run dev" # string → sh -c inside the container
worker:
image: node:20
command: ["node", "worker.js"] # list → direct exec, no shell

Caveat: images with an ENTRYPOINT (e.g. python, node, postgres) prepend it to the command. Shell-form command: "script.py" on a python image becomes python /bin/sh -c "script.py" — use the list form or a custom image when this matters.

Requirement for shell form: the image must contain /bin/sh (most do; scratch and some distroless images do not).

Signal handling: with shell form, /bin/sh is PID 1. docker stop sends SIGTERM to sh, which may not forward it to the child — the container waits out the stop grace period before SIGKILL. Use the list form for fast graceful shutdown on a single binary.

For data services that have no HTTP endpoint (databases, caches):

services:
db:
image: postgres:16
health:
command: "pg_isready -U myapp" # string → sh -c wrap
interval: 2s
timeout: 1s
retries: 10
cache:
image: redis:7
health:
command: ["redis-cli", "ping"] # array → exec directly (no shell)
interval: 1s
retries: 5

path and command are mutually exclusive — use one or the other.

Containers in the same lokl project share a bridge network (lokl-{name}). They can reach each other by service name as hostname — no need to expose ports between containers:

services:
api:
image: myapp:latest
env:
DB_HOST: db # reaches the "db" container directly
REDIS_HOST: cache # reaches the "cache" container directly
depends_on:
- db
- cache
db:
image: postgres:16
health:
command: "pg_isready -U myapp"
retries: 10
cache:
image: redis:7
health:
command: ["redis-cli", "ping"]
retries: 5

The network is created on lokl up and removed on lokl down.

Load variables from .env files instead of hardcoding secrets:

# Global — available to all services
env_file:
- .env
services:
api:
command: pnpm dev
env_file:
- .env.local

Standard dotenv format: KEY=VALUE, # comments, blank lines, optional quotes. Paths are relative to lokl.yaml.

Inline env values take priority over env_file values.

Reference host environment variables with ${VAR} or $VAR:

services:
api:
command: pnpm dev
env:
DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@localhost:5432/mydb

Variables are resolved against the host’s environment at config load time. Missing variables expand to an empty string.

A service entry with proxy_only: true registers a subdomain route without starting a process or container. Useful for:

  • One container, multiple HTTPS endpoints — MinIO API + console, Postgres + pgAdmin, Jaeger UI on a second port.
  • Host-native processes — a Go server or Python script you run manually; lokl gives it a trusted HTTPS URL without managing its lifecycle.
services:
minio:
image: minio/minio:latest
ports: ["9000:9000", "9001:9001"]
port: 9000
subdomain: s3
minio-console:
proxy_only: true
subdomain: console
port: 9001
depends_on: [minio]
  • port — the target port lokl forwards to (always 127.0.0.1:<port> over HTTP).
  • subdomain — scalar or list; wildcards like "*.x.test" work the same as for regular services.
  • depends_on — wait for another service to be healthy before probing.
  • rewrite.strip_prefix / rewrite.fallback — path aliasing, same as normal services.
  • health.path — HTTP readiness probe instead of the default TCP probe.

command, image, ports, volumes, env, env_file, autostart, restart, ready_timeout, limits, health.command. Validation rejects these with a clear error message.

  • The target port can belong to a lokl-managed container (via its ports: publish) or to a host-native process the user runs themselves — lokl just dials 127.0.0.1:<port>.
  • The duplicate-port validator ignores proxy-only services — they forward, not bind.
  • In the TUI, proxy-only services appear in the service list with a TCP-probe-backed status dot. The restart key is a no-op (no process to restart). The log panel shows a static banner.