Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

devhub.toml Reference

The devhub.toml file is the project manifest that tells DevHub how to manage your project’s services.

Basic Structure

[project]
name = "my-project"
description = "My awesome project"
tags = ["rust", "web", "api"]
env_files = [".env", ".env.local"]

[[services]]
name = "api"
type = "rust-binary"
command = "cargo run --release"
port = 8080
health_check = "/health"
subdomain = "api"
env_file = "api/.env"

[[services]]
name = "frontend"
type = "node"
command = "npm run dev"
cwd = "frontend"
port = 3000
main = true
depends_on = ["api"]

[environment]
RUST_LOG = "info"
DATABASE_URL = "postgres://localhost/mydb"

[project] Section

Project-level metadata.

FieldTypeRequiredDescription
namestringYesUnique project identifier
descriptionstringNoHuman-readable description
tagsarrayNoTags for organization/filtering
env_filesarrayNoEnvironment files to load for all services

Example

[project]
name = "my-fullstack-app"
description = "Full-stack application with API and frontend"
tags = ["typescript", "react", "node", "postgres"]
env_files = [".env", ".env.local"]

[[services]] Section

Define one or more services. Each service represents a runnable process.

FieldTypeRequiredDefaultDescription
namestringYes-Service identifier
typestringYes-Service type (see below)
commandstringYes-Command to run
portintegerYes-Port the service listens on
cwdstringNoproject rootWorking directory (relative)
health_checkstringNo-HTTP endpoint for health checks
subdomainstringNo-Subdomain for reverse proxy
mainbooleanNofalsePrimary service (no subdomain)
depends_onarrayNo[]Services to start first
envtableNo{}Service-specific env vars
env_filestringNo-Service-specific env file path

Service Types

TypeDetectionDefault Behavior
rust-binaryCargo.tomlNative process
nodepackage.jsonPM2 if available, otherwise native
pythonpyproject.tomlNative process
gogo.modNative process
docker-composedocker-compose.ymlDocker Compose commands
shellAnyNative process

Example Service

[[services]]
name = "api"
type = "rust-binary"
command = "cargo run --bin api --release"
port = 8080
health_check = "/api/health"
subdomain = "api"
env = { "RUST_LOG" = "debug", "DATABASE_URL" = "postgres://localhost/mydb" }
env_file = "api/.env"

[environment] Section

Global environment variables applied to all services.

[environment]
NODE_ENV = "development"
RUST_LOG = "info"
DATABASE_URL = "postgres://localhost/mydb"
API_KEY = "dev-key-123"

Environment Loading Priority

Environment variables are loaded in this order (later sources override earlier):

  1. System environment variables
  2. Project root .env file
  3. Project root .env.local file
  4. Files listed in env_files
  5. [environment] section in manifest
  6. Service-specific env_file
  7. Service cwd/.env file
  8. Service cwd/.env.local file
  9. Service env = {} section
  10. DevHub service URL injection
  11. Service reference expansion

Variable Interpolation

Variables can reference other variables using $VAR or ${VAR} syntax:

[environment]
DB_HOST = "localhost"
DB_PORT = "5432"
DB_NAME = "myapp"
DATABASE_URL = "postgres://${DB_HOST}:${DB_PORT}/${DB_NAME}"

Service URL Environment Variables

DevHub automatically injects environment variables for service-to-service communication. This eliminates the need to hardcode localhost:PORT in your .env files.

Auto-Injected Variables

For every service in your project, DevHub injects:

VariableExampleDescription
DEVHUB_<SERVICE>_URLhttp://api.myapp.localhostExternal URL via Caddy reverse proxy
DEVHUB_<SERVICE>_INTERNAL_URLhttp://myapp-api:8080Internal URL for container-to-container
DEVHUB_<SERVICE>_PORT8080Service port

Service names are converted to uppercase with hyphens replaced by underscores (e.g., my-apiMY_API).

View all injected variables with:

devhub env my-project --service frontend | grep DEVHUB_

Service Reference Syntax

You can reference other services in your devhub.toml using the ${service.property} syntax:

[[services]]
name = "frontend"
type = "node"
command = "npm run dev"
port = 3000
main = true

[services.env]
# These get auto-expanded by DevHub:
NEXT_PUBLIC_API_URL = "${backend.url}"        # → http://backend.myapp.localhost
INTERNAL_API_URL = "${backend.internal}"      # → http://myapp-backend:8080
API_PORT = "${backend.port}"                  # → 8080

Available properties:

PropertyDescription
${service.url}External URL via Caddy (for browser/client-side requests)
${service.internal}Internal URL for container-to-container communication
${service.port}Service port number

Use Case: Frontend + Backend

A common pattern is a Next.js frontend calling a Python/Node/Rust backend:

[project]
name = "my-fullstack-app"

[[services]]
name = "backend"
type = "python"
command = "uvicorn main:app --host 0.0.0.0 --port 8081"
port = 8081
subdomain = "api"

[[services]]
name = "frontend"
type = "node"
command = "npm run dev"
port = 3000
main = true
depends_on = ["backend"]

[services.env]
# Client-side API calls go through Caddy
NEXT_PUBLIC_API_URL = "${backend.url}"

The frontend automatically receives NEXT_PUBLIC_API_URL=http://api.my-fullstack-app.localhost.

Benefits:

  • No hardcoded localhost:PORT in .env files
  • Works consistently across native and container modes
  • URLs update automatically based on project name
  • Supports both browser requests (external URLs) and server-side requests (internal URLs)

Monorepo Configuration

For projects with multiple services in subdirectories:

[project]
name = "my-monorepo"
description = "Fullstack monorepo"
env_files = [".env"]

[[services]]
name = "api"
type = "python"
command = "uvicorn app:app --host 0.0.0.0 --port 8000"
cwd = "backend"
port = 8000
subdomain = "api"

[[services]]
name = "web"
type = "node"
command = "npm run dev"
cwd = "frontend"
port = 3000
main = true
depends_on = ["api"]

[[services]]
name = "admin"
type = "node"
command = "npm run dev"
cwd = "admin-panel"
port = 3001
subdomain = "admin"
depends_on = ["api"]

Service Dependencies

Use depends_on to control startup order:

[[services]]
name = "database"
type = "docker-compose"
command = "docker compose up -d postgres"
port = 5432

[[services]]
name = "api"
type = "rust-binary"
command = "cargo run"
port = 8080
depends_on = ["database"]

[[services]]
name = "frontend"
type = "node"
command = "npm run dev"
port = 3000
depends_on = ["api"]

Services start in dependency order: database → api → frontend

Complete Example

[project]
name = "ecommerce-platform"
description = "Full-stack e-commerce application"
tags = ["typescript", "rust", "postgres", "redis"]
env_files = [".env", ".env.local"]

[[services]]
name = "postgres"
type = "docker-compose"
command = "docker compose up -d postgres"
port = 5432

[[services]]
name = "redis"
type = "docker-compose"
command = "docker compose up -d redis"
port = 6379

[[services]]
name = "api"
type = "rust-binary"
command = "cargo run --bin api --release"
cwd = "services/api"
port = 8080
health_check = "/health"
subdomain = "api"
depends_on = ["postgres", "redis"]
env_file = "services/api/.env"

[[services]]
name = "worker"
type = "rust-binary"
command = "cargo run --bin worker --release"
cwd = "services/worker"
port = 8081
depends_on = ["postgres", "redis"]

[[services]]
name = "storefront"
type = "node"
command = "npm run dev"
cwd = "apps/storefront"
port = 3000
main = true
depends_on = ["api"]

[[services]]
name = "admin"
type = "node"
command = "npm run dev"
cwd = "apps/admin"
port = 3001
subdomain = "admin"
depends_on = ["api"]

[environment]
NODE_ENV = "development"
RUST_LOG = "info"
DATABASE_URL = "postgres://localhost:5432/ecommerce"
REDIS_URL = "redis://localhost:6379"