# Build the Docker image

Agent Mission Control developer docs > Claude skill, self-hosted

> The generated Dockerfile, the skill-specific prep patterns, the non-root user, and the port convention.

**Before you start:** A generated agent directory and Docker.

The Dockerfile builds from the **repository root**, so the build context can reach both the agent directory and the source skill. Here it is filled in for a skill that writes reports to `/outputs`:

`Dockerfile`
```dockerfile
# Report Generator Agent: Claude Agent SDK + report-generator skill
# Build from the repo root:
#   docker build -f azurefoundry_report_generator_skills/Dockerfile -t report-generator-skills-agent .
FROM python:3.12-slim

WORKDIR /app

# No Node.js: claude-agent-sdk>=0.2.138 bundles the Claude Code CLI inside the wheel.
RUN apt-get update \
    && apt-get install -y --no-install-recommends bash curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Skill-specific prep ({{DOCKERFILE_SKILL_PREP_BLOCK}}): a writable outputs dir
RUN mkdir -p /outputs && chmod 777 /outputs

RUN pip install --no-cache-dir uv

# Python deps first, for layer caching
COPY azurefoundry_report_generator_skills/pyproject.toml azurefoundry_report_generator_skills/uv.lock* ./
RUN uv pip install --system -e .

# The source skill, into the SDK discovery path
COPY skills/report-generator /app/.claude/skills/report-generator

# Agent code
COPY azurefoundry_report_generator_skills/ /app/

# Non-root user ({{NONROOT_EXTRA_DIRS}} = /outputs)
RUN useradd --create-home --shell /bin/bash --uid 1000 claude \
    && chown -R claude:claude /app /home/claude /outputs
USER claude

ENV HOME=/home/claude
ENV AGENT_PORT=9024
ENV AGENT_HOST=0.0.0.0
ENV REPORT_OUTPUTS_DIR=/outputs
ENV CLAUDE_AGENT_CWD=/app
ENV CLAUDE_AGENT_MODEL=claude-sonnet-4-6

EXPOSE ${AGENT_PORT}

CMD ["sh", "-c", "uv run . --host ${AGENT_HOST} --port ${AGENT_PORT}"]
```

- `pyproject.toml` is what the image installs from. It never reads `requirements.txt`, so the `claude-agent-sdk>=0.2.138` floor that matters lives in `pyproject.toml`. Keep the two files in step.
- Do not lower the SDK floor. Releases below 0.2.138 need Node.js, which the image no longer installs.
- The skill is copied unchanged to `/app/.claude/skills/<skill-name>/`, where `setting_sources=["project"]` finds it.

## Skill prep patterns

Replace `{{DOCKERFILE_SKILL_PREP_BLOCK}}` with whichever of these the skill needs. Concatenate several if needed.

### Hardcoded session paths

Some skills written in a Claude Cowork session hardcode a session-specific path such as `/sessions/<session-id>/mnt/outputs/...` in their scripts. Create the exact path; do not try to detect it dynamically.

```dockerfile
RUN mkdir -p /sessions/<exact-id-from-skill>/mnt/outputs/<rest-of-path> \
    && chmod -R 777 /sessions
```

### A CLI binary the skill calls

```dockerfile
ARG TOOL_VERSION=1.2.3
RUN curl -fsSL "https://github.com/<org>/<repo>/releases/download/v${TOOL_VERSION}/<asset>.tar.gz" \
      -o /tmp/tool.tgz \
    && tar -xzf /tmp/tool.tgz -C /tmp \
    && mv /tmp/<bin-name> /usr/local/bin/<bin-name> \
    && chmod +x /usr/local/bin/<bin-name> \
    && rm /tmp/tool.tgz
```

If the skill's bootstrap looks for the binary at a specific path, put it exactly there instead of on `PATH`.

### A writable outputs directory

```dockerfile
RUN mkdir -p /outputs && chmod 777 /outputs
ENV REPORT_OUTPUTS_DIR=/outputs
```

Mount a file share at `/outputs` at deploy time so files survive container restarts.

### A token or credentials directory

```dockerfile
ENV REPORT_DIR=/mnt/report
```

Set only the env var. Do not create the directory in the image: a file share is mounted there at deploy time. Document the share setup in the README.

### System packages

```dockerfile
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        bash curl ca-certificates \
        jq pandoc \
    && rm -rf /var/lib/apt/lists/*
```

Extend the existing `apt-get install` step instead of adding a second one, to keep layers small. Do not add Node.js back.

### Python deps for skill scripts

Add what the skill's scripts import to `{{SKILL_EXTRA_DEPS}}` in `pyproject.toml` (for example `"feedparser>=6.0.10"`) and mirror it in `requirements.txt`. No Dockerfile change is needed.

## The non-root user

The template always runs as user `claude` (uid 1000). Add every extra directory the skill writes to `{{NONROOT_EXTRA_DIRS}}`, for example `/sessions /outputs` or `/sessions /mnt/<name> /outputs`. If you skip the `chown`, the agent starts but fails with `Permission denied` when the skill writes.

Staying non-root also protects the permission model. The bundled CLI refuses `--dangerously-skip-permissions`, which `permission_mode="bypassPermissions"` translates to, when it runs as root. An agent that switched to `bypassPermissions` and ran as root would start and pass `/health`, then fail every run with `--dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons`.

## What stays out of the image

- **Secrets.** Never `ENV ANTHROPIC_API_KEY=...` and never `COPY .env .`. Credentials come at runtime from Container Apps secrets or `--env-file`.
- **`tests/`.** `.dockerignore` excludes it. Keep that line.
- **Local edits to the skill.** Copy the skill as it lives in the repo. Patch the filesystem or the env, not the skill.

`.dockerignore`
```text
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.venv/
venv/
.env
*.log
.git/
.gitignore
README.md
tests/
```

## Ports

| Surface | Port |
| --- | --- |
| Plain HTTP | The lowest unused port at or above `9024`, recorded in a port list kept with your agents. `9000`–`9023` are reserved for the orchestrator, websocket server, and frontend dev server; `9100` and up for non-A2A internal services. |
| AMC variant | `8080`, with no port list entry needed. |

Never invent a port for a plain-HTTP agent: check the list first and add the new agent to it in the same change.

## Related

- [Configuration and environment](https://www.agentmissioncontrol.ai/developers/self-hosted-configuration/)
- [Deploy and register in AMC](https://www.agentmissioncontrol.ai/developers/self-hosted-deploy-and-register/)
- [Common quirks and troubleshooting](https://www.agentmissioncontrol.ai/developers/self-hosted-troubleshooting/)

---
Source: https://www.agentmissioncontrol.ai/developers/self-hosted-docker/ · Last updated 2026-10-21 · Full index: https://www.agentmissioncontrol.ai/llms.txt
