> ## Documentation Index
> Fetch the complete documentation index at: https://ona.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Container configuration

> Standardize development environments across your team with Dev Container configuration.

Ona fully supports [Development Containers](https://containers.dev/). Define your setup in a `devcontainer.json` file to get standardized, version-controlled environments with consistent tooling across your team, whether single-container or multi-container, with VS Code and JetBrains support.

For how environments and Dev Containers fit into the broader platform, see [Core Components](/ona/understanding/core-components#environments).

## Configuration

### File Location

Place your `devcontainer.json` file in one of these standard locations:

* `.devcontainer/devcontainer.json`
* `.devcontainer.json`

### Basic Configuration Example

```json theme={null}
{
	"name": "My Project",
	"image": "mcr.microsoft.com/devcontainers/universal:4.0.1-noble",
	"features": {
		"ghcr.io/devcontainers/features/docker-in-docker:2": { "moby": false }
	},
	"customizations": {
		"vscode": {
			"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
			"settings": {
				"editor.formatOnSave": true
			}
		},
		"jetbrains": {
			"plugins": ["com.wix.eslint", "intellij.prettierJS"]
		}
	},
	"forwardPorts": [3000, 8080]
}
```

This configuration:

* Uses the Universal image with common languages and tools pre-installed (Node.js, Python, Go, Java, and more)
* Adds Docker-in-Docker support
* Includes ESLint and Prettier VS Code extensions
* Installs the ESLint & PrettierJS plugin for JetBrains IDEs
* Configures auto-formatting on save
* Declares ports 3000 and 8080 for [IDE-managed forwarding](#forward-ports)

### Forward ports

Use `forwardPorts` in `devcontainer.json` to tell supported IDEs which ports to forward from the Dev Container to `localhost` on your local machine. As the port-forwarding is performed by your IDE the ports are not accessible to anyone else. If you need a shareable URL or want to access a port in the environment without using an IDE, use Ona [Port sharing](https://ona.com/docs/ona/integrations/ports#port-sharing).

See the official [Dev Container JSON reference](https://containers.dev/implementors/json_reference/#general-properties) for more details about port-related properties.

## Multiple Dev Container configurations

You can manage multiple Dev Container configurations using Ona projects. This allows you to define different environments for:

* Different branches or repositories
* Various development scenarios
* Specialized tasks requiring specific tools

## Known limitations

When using Dev Containers with Ona, be aware of these limitations:

* Conflicting features can cause build failures (Ona will display an error message)
* Some Dev Container commands might behave differently in Ona's environment
* When build or startup errors occur, [recovery mode](#recovery-mode) is engaged, requiring manual intervention

## Recommended images

Microsoft provides well-maintained Dev Container base images for popular development stacks:

* `mcr.microsoft.com/devcontainers/universal:4.0.1-noble` - Full-featured image with common languages and tools
* `mcr.microsoft.com/devcontainers/javascript-node` - Node.js development
* `mcr.microsoft.com/devcontainers/python` - Python development
* `mcr.microsoft.com/devcontainers/dotnet` - .NET development
* `mcr.microsoft.com/devcontainers/java` - Java development
* `mcr.microsoft.com/devcontainers/go` - Go development
* `mcr.microsoft.com/devcontainers/base:2.0.4-noble` - Minimal image

## Advanced configuration

### Using secrets during builds

[Organization](/ona/organizations/organization-secrets) and [project](/ona/projects/project-secrets) secrets can be used during Dev Container image builds via [secret mounts](https://docs.docker.com/build/building/secrets/#secret-mounts). This is useful when your Dockerfile needs to authenticate with private package registries, pull licensed dependencies, or access credentials that should not be baked into the final image.

Secrets are not exposed by default. Each `RUN` instruction in your Dockerfile must explicitly request the secrets it needs using the `--mount=type=secret` flag. The mounted secret itself is only available for the duration of that `RUN` step and is not included in the image layer.

<Warning>
  The secret mount is ephemeral, but commands within the `RUN` step can still write secret values to files or configuration stores that **are** persisted in the image. If you don't want a credential to be available in the resulting image, avoid commands like `npm config set` that write credentials to disk. Use the `env=` mount pattern or clean up any written files before the `RUN` step ends.
</Warning>

User secrets are not available during builds. Built images are cached and shared across the project when the [Dev Container image cache](/ona/runners/devcontainer-image-cache) is enabled, so personal credentials in a build could be exposed to other team members.

<Note>
  Build-time secret mounts require a Dockerfile-based Dev Container (`build.dockerfile` or `dockerFile` in `devcontainer.json`). Docker Compose-based Dev Containers are not yet supported.
</Note>

#### How to reference a secret

The `id` in `--mount=type=secret,id=...` must match how the secret is configured in Ona. For file secrets, the `id` is the full file path. For environment variable secrets, the `id` is the variable name.

Use `target=` to control where the secret is mounted during the `RUN` step. The `target` path can be any location your build step needs. It does not have to match the `id`.

#### Example: private npm registry (file mount)

Given a file secret configured in Ona at `/usr/local/secrets/npm-token`:

```dockerfile theme={null}
FROM node:20
COPY package.json package-lock.json ./

RUN --mount=type=secret,id=/usr/local/secrets/npm-token,target=/tmp/npm-token \
    echo "//registry.npmjs.org/:_authToken=$(cat /tmp/npm-token)" > .npmrc && \
    npm ci && \
    rm -f .npmrc
```

The `.npmrc` file is written, used for `npm ci`, and removed within the same `RUN` step so the token is not persisted in the image layer.

#### Example: private npm registry (env mount)

The `env=` option mounts the secret directly as an environment variable, avoiding the need to read from a file. This requires Dockerfile syntax v1.10.0+, so you must add `# syntax=docker/dockerfile:1` as the very first line of your Dockerfile (before any other instructions or comments):

```dockerfile theme={null}
# syntax=docker/dockerfile:1
FROM node:20
COPY package.json package-lock.json ./

RUN --mount=type=secret,id=NPM_TOKEN,env=NPM_TOKEN \
    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
    npm ci && \
    rm -f .npmrc
```

<Tip>
  The `# syntax=docker/dockerfile:1` directive pulls the `docker/dockerfile:1` image from Docker Hub to use as the build frontend. If your environment restricts access to Docker Hub, you can mirror the image to a private registry and reference it instead:

  ```dockerfile theme={null}
  # syntax=my-registry.example.com/docker/dockerfile:1
  ```
</Tip>

No changes to `devcontainer.json` are needed. Ona passes the secrets to the build process, and your Dockerfile controls which `RUN` steps can access them.

### Multi-Container Development

For more complex setups, you can define multiple containers using Docker Compose.

**devcontainer.json:**

```json theme={null}
{
	"name": "Multi-container App",
	"dockerComposeFile": "docker-compose.yml",
	"service": "app",
	"workspaceFolder": "/workspace",
	"customizations": {
		"vscode": {
			"extensions": ["ms-azuretools.vscode-docker"]
		}
	}
}
```

**docker-compose.yml:**

```yaml theme={null}
version: "3.8"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ..:/workspace:cached
    network_mode: host
    command: sleep infinity

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    network_mode: host
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: app_dev
```

<Warning>
  **Required:** Set `network_mode: host` on all services. Without this, services attempt to bridge networks, which can lock you out of your dev container with no way to recover except deleting the environment.
</Warning>

### Adding Custom Features

[Dev Container Features](https://containers.dev/features) are self-contained, shareable units of installation code and configuration that let you quickly add tooling, runtimes, or libraries to your development container.

You can add features to your Dev Container by adding them to the `features` section of your `devcontainer.json` file:

```json theme={null}
{
	"features": {
		"ghcr.io/devcontainers/features/docker-in-docker:2": { "moby": false },
		"ghcr.io/devcontainers/features/github-cli:1": {}
	}
}
```

Ona works well with many Dev Container Features, especially [official ones](https://containers.dev/features) designed for cloud-based environments. Linux-based runners generally provide best compatibility with most Dev Container Features. Here's what you should know:

* Community-supported features might require additional testing, as they may have been developed without specific consideration for compatibility.
* Feature behavior can vary depending on base images, other installed features, and specific configurations in your setup.

## Recovery mode

Recovery mode keeps the environment accessible when the configured Dev Container fails to build or start. Ona starts a temporary recovery container, mounts your workspace, and exposes it through the normal connection path so you can inspect logs, edit the Dev Container configuration, and rebuild.

Recovery mode does not apply your failed Dev Container image, features, lifecycle commands, or editor customizations. Use it only to repair the configuration. After a successful rebuild, Ona removes the temporary recovery container and reconnects to the rebuilt Dev Container.

When you enter recovery mode:

1. View the build logs:

   ```bash theme={null}
   ona environment devcontainer logs
   ```

2. Fix the relevant Dev Container files, such as `.devcontainer/devcontainer.json`, `.devcontainer/Dockerfile`, or `.devcontainer/docker-compose.yml`.

3. Rebuild the Dev Container:

   ```bash theme={null}
   ona environment devcontainer rebuild
   ```

## Next steps

* Explore the full [Dev Container specification](https://containers.dev/implementors/spec/) for advanced configurations
* Check out the [Dev Container Feature catalog](https://containers.dev/features) for additional tools and utilities
* Learn about [Ona projects](/ona/projects/overview) to manage multiple environments

## Troubleshooting

<Accordion title="Dev Container fails to build">
  1. Use the "Ask Ona" button in the startup accordion to get help diagnosing the failure. Ona analyzes the error logs and suggests fixes for common issues such as privileged operations, sudo commands, or image pull failures. This feature is available when Ona AI is enabled and your runner supports LLM capabilities.
  2. Check the Ona console for specific error messages.
  3. Ensure image versions are correctly specified.
  4. If the environment enters [recovery mode](#recovery-mode), fix the configuration and rebuild the Dev Container.
</Accordion>

<Accordion title="Debugging lifecycle commands">
  If a lifecycle command like `postCreateCommand` appears to run inconsistently, view lifecycle command output in the environment logs under "Running dev container background commands".

  Alternatively, you can also add logging to your script:

  ```bash theme={null}
  exec >> /workspaces/post-create.log
  exec 2>&1
  set -x
  ```

  Check `/workspaces/post-create.log` to see exactly what ran.

  As an alternative to `postCreateCommand`, consider using [Tasks and Services](/ona/configuration/tasks-and-services/overview) which provide UI visibility, CLI access, and structured triggers.
</Accordion>
