Ona loads tasks and services from .ona/automations.yaml in your repository root. You can change this location in Projects.
services:
# long-running processes (databases, servers)
tasks:
# one-off actions (build, test, seed)
The key (e.g., database, buildAll) is used to reference the item in dependencies and CLI commands. Keys must match the pattern ^[a-zA-Z0-9_-]{1,128}$ (alphanumeric, underscores, and hyphens, 1-128 characters).
Service schema
Services are long-running processes that stay active throughout your session.
Service fields
| Field | Type | Required | Description |
|---|
name | string | Yes | Display name shown in UI and logs |
description | string | No | Description of what the service does |
commands | object | No | Lifecycle commands (see below) |
triggeredBy | array | No | When to automatically start (see Triggers) |
role | string | No | Service role: default, editor, or ai-agent |
runsOn | object | No | Execution environment (see Execution environment) |
Service commands
The commands object controls the service lifecycle:
| Field | Required | Description |
|---|
start | Yes (if commands defined) | Command to start and run the service. Must stay running: the service is active only while this process is alive. If it exits with code 0, the service transitions to Stopped. Non-zero exit transitions to Failed. |
ready | No | Readiness check command. Runs repeatedly until it exits with code 0. Service stays in Starting phase until ready succeeds. If not set, the service is considered ready as soon as start begins running. |
stop | No | Custom stop command. If not set, start receives SIGTERM when stop is requested. If set, stop runs first, then start receives SIGKILL. |
When stopping a service, if the process doesn’t exit within 2 minutes, SIGKILL is sent automatically.
Service example
services:
database:
name: PostgreSQL
description: The backend database
triggeredBy:
- postEnvironmentStart
commands:
start: docker run --rm --name database postgres:latest
ready: docker exec database pg_isready
stop: docker stop database
backend:
name: Application Backend
description: The application backend
role: default
triggeredBy:
- postEnvironmentStart
commands:
start: cd backend && go run main.go
Service phases
Services transition through these phases during their lifecycle:
| Phase | Description |
|---|
STARTING | Start command running, readiness check pending (if configured) |
RUNNING | Service is running and ready |
STOPPING | Service is being stopped |
STOPPED | Service stopped normally (exit code 0) |
FAILED | Service failed (non-zero exit code) |
Task schema
Tasks are one-off actions that run to completion.
Task fields
| Field | Type | Required | Description |
|---|
name | string | Yes | Display name shown in UI and logs |
command | string | Yes | Shell command to execute |
description | string | No | Description of what the task does |
triggeredBy | array | No | When to automatically run (see Triggers) |
dependsOn | array | No | Task keys that must complete before this task starts. In a dependency chain, only the final task should have an automatic trigger - dependent tasks are started automatically when needed. |
runsOn | object | No | Execution environment (see Execution environment) |
Task example
tasks:
buildAll:
name: Build All
description: Builds all code
command: go build .
runUnitTests:
name: Run unit tests
command: go test -v ./...
dependsOn:
- buildAll
validate:
name: Validate
description: Builds and tests the code
triggeredBy:
- postEnvironmentStart
dependsOn:
- buildAll
- runUnitTests
command: echo "Validation complete"
Task execution phases
Task executions transition through these phases:
| Phase | Description |
|---|
PENDING | Task waiting to start |
RUNNING | Task is executing |
SUCCEEDED | Task completed successfully (exit code 0) |
FAILED | Task failed (non-zero exit code) |
STOPPED | Task was stopped before completion |
Triggers
Control when tasks and services run automatically:
| Trigger | Services | Tasks | Description |
|---|
manual | ✓ | ✓ | Triggered by explicit user action via CLI or UI |
postDevcontainerStart | ✓ | ✓ | After the Dev Container starts in a user environment (first start or rebuild). Does not fire during prebuilds. |
postEnvironmentStart | ✓ | ✓ | Every time the environment starts or resumes |
prebuild | ✓ | ✓ | During prebuild execution only. Does not fire when a user environment starts from a prebuild. No user secrets available. |
User secrets are not available during prebuild execution because prebuilds run without user context.
Additional triggers (API only)
The following triggers can only be set via the API, not in automations.yaml:
| Trigger | Services | Tasks | Description |
|---|
postMachineStart | ✓ | ✓ | After the VM starts, before the Dev Container is ready. Requires runsOn: machine. Used for machine-level services like security agents. |
beforeSnapshot | ✗ | ✓ | After all prebuild tasks complete, before the snapshot is taken. Used for tasks that must run last during prebuilds, such as IDE warmup. |
Prebuilds and triggers
During a prebuild, only automations (tasks and services) with the prebuild trigger run. All other triggers (postDevcontainerStart, postEnvironmentStart) are skipped. This is intentional: prebuilds run without user context (no user secrets), so running automations during a prebuild is opt-in.
When a user creates an environment from a prebuild, the normal triggers fire: postDevcontainerStart, postEnvironmentStart, etc. The prebuild trigger does not fire in user environments.
Common pattern — tasks: Use both triggers on the same task when you want it to run during prebuilds and when the Dev Container is rebuilt in a user environment:
tasks:
install-deps:
name: Install dependencies
triggeredBy:
- prebuild
- postDevcontainerStart
command: npm ci
In this example, npm ci runs during the prebuild (so the snapshot includes node_modules) and also runs if a user rebuilds their Dev Container (which creates a fresh container without the snapshot).
Common pattern — services: Start a service during prebuilds so that prebuild tasks can depend on it:
services:
database:
name: Database server
commands:
start: docker run --rm --name database -p 5432:5432 postgres:16
ready: docker exec database pg_isready
triggeredBy:
- prebuild
- postDevcontainerStart
The prebuild waits for all services to become ready before snapshotting. Without a ready command, the service is considered ready immediately, so the prebuild may snapshot before the service has finished initializing (e.g., before a Docker image is fully pulled). Always define a ready command for prebuild services so that setup work is captured in the snapshot. If the service fails, the prebuild still completes.
Execution environment
By default, tasks and services run inside the Dev Container. The runsOn field lets you change where they execute.
Run on the host machine
Use runsOn: machine to run directly on the VM, outside the Dev Container. This is useful for services that need to start before the Dev Container is ready (e.g., with the postMachineStart API trigger) or that need direct access to the host.
services:
security-agent:
name: Security Agent
commands:
start: /opt/agent/run
runsOn:
machine: {}
Run in a Docker container
Use runsOn: docker to run in a separate Docker container with a specific image:
tasks:
lint:
name: Lint
command: golangci-lint run ./...
runsOn:
docker:
image: golangci/golangci-lint:latest
environment:
- GOLANGCI_LINT_CACHE=/tmp/cache
| Field | Type | Required | Description |
|---|
docker.image | string | Yes | Docker image to run in |
docker.environment | array | No | Environment variables to set (format: KEY=VALUE) |
Complete example
This example demonstrates all available schema features:
services:
# Containerized service using docker run
redis:
name: Redis Cache
description: In-memory cache for session data
role: default
triggeredBy:
- postDevcontainerStart
commands:
start: |
if docker inspect redis >/dev/null 2>&1; then
docker start -a redis
else
docker run --name redis -p 6379:6379 redis:7-alpine redis-server --appendonly yes
fi
ready: docker exec redis redis-cli ping | grep -q PONG
stop: docker stop redis
# Service running in the Dev Container
backend:
name: API Server
description: Main application backend
triggeredBy:
- postDevcontainerStart
- manual
commands:
start: cd backend && go run main.go
ready: curl -sf http://localhost:8080/health
# AI agent service
code-assistant:
name: Code Assistant
description: AI-powered code analysis
role: ai-agent
triggeredBy:
- manual
commands:
start: ./bin/assistant serve
tasks:
# Task that runs during prebuild (no user secrets available)
install-deps:
name: Install dependencies
description: Install all project dependencies
triggeredBy:
- prebuild
- postDevcontainerStart
command: npm ci && go mod download
# Task with dependencies on other tasks
build:
name: Build project
description: Compile all source code
dependsOn:
- install-deps
command: npm run build && go build ./...
# Lint task
lint:
name: Lint code
description: Run linters
command: golangci-lint run ./...
# Manual task for running tests
test:
name: Run tests
description: Execute the full test suite
triggeredBy:
- manual
dependsOn:
- build
command: npm test && go test -v ./...
Iterating on Tasks and Services
You can iterate on tasks and services using the CLI which is available by default in every Ona environment. The CLI can
- reload the tasks and services file using:
ona automations update [optional-path-to-automations.yaml]
ona automations service start ...
ona automations task start ...
Using Tasks and Services outside of an environment
The CLI commands to interact with an environment’s tasks and services are also available outside of an environment. The following snippet brings up an environment, adds a task, runs it, waits for the task to complete and brings the environment back down again:
# ona env create will set the environment context to the newly created env
ona env create https://github.com/some/repo
# add the task to the environment
cat <<EOF | ona automations update -
tasks:
build:
command: go build ./...
EOF
# run it
ona automations task start build
# stop the environment
ona env stop