# Container apps

Use this guide to install and register apps inside an existing Outer Shell
container, whether you edit its Dockerfile directly or use a coding agent.

## Connect to the container

In Outer Shell, choose **Add more… → Coding agent** under the container and
copy the prompt. Outer Loop includes the SSH command and the command to enter
that specific container. You can also use the container terminal's **Copy SSH
command** action. Run that complete command from your computer; a host-only SSH
connection does not enter the container.

If your coding agent already runs inside the container, work there directly.
Check the environment before installing anything. The instructions below assume
an Outer Shell-enabled image with `outerctl` installed. Host apps use the
[general integration API](https://outershell.org/api/) instead.

## Configure the container

You are configuring an OCI container managed by Outer Shell. You may install
software, create services, and otherwise change this container as needed for
the user's request.

Use the publishing reference below for app integration. Your working directory is /work;
keep experiments and notebooks here unless the user selects another mounted or
persistent directory.

## Preserve the result

The reproducible source of truth is the ordinary Docker build context mounted
at `/var/lib/outershell/project`. Its Dockerfile is:

```text
/var/lib/outershell/project/Dockerfile
```

When a requested change should survive a rebuild or move to another server,
edit that Dockerfile and any files beside it. Prefer portable, noninteractive,
idempotent Dockerfile instructions. A root process may edit this project
directly. Do not create a second recipe or initialization system.

After recording a durable change, prefer to mirror it in the running container
so the user can use it immediately. For example, install the same packages,
write the same configuration, create the same directories, and start or
restart the affected service in the live instance. The Dockerfile remains the
source of truth; a live-only change is not complete.

You have two acceptable ways to finish a durable change:

1. Preferably, update the Dockerfile and mirror the result in the live
   container. Tell the user that the change is available now and will also be
   reproduced by future rebuilds.
2. Ask the user to rebuild the container when mirroring is impossible, unsafe,
   or significantly more difficult than rebuilding. Explain why the rebuild is
   needed.

Do not make the user rebuild merely because the Dockerfile changed. Build-only
instructions such as `COPY`, `ENV`, `USER`, and `VOLUME` may require a rebuild,
but mirror their intended effect when a safe runtime equivalent exists. Do not
run Docker, `container`, or another host container engine from inside this
container.

Folder mounts, persistent volumes, environment variables, and secrets are
runtime configuration owned by Outer Shell. Do not add, remove, or change host
mounts from inside the container. Treat explicitly mounted user folders as
user data and modify their contents only when the request calls for it. Never
put credentials or secret values in the Dockerfile or image.

## Publish tools in Outer Shell

Outer Shell support is installed in this image. Use build-time `outerctl image`
commands in the Dockerfile so published tools return after every rebuild:

- `outerctl image add-app` publishes a browser or Outerframe app backed by a
  managed service.
- `outerctl image add-command` publishes a command-line tool whose launch
  command can be copied from Outer Shell.

Read the publishing reference below before adding or changing a published tool. Use stable, descriptive IDs. Reference icon files by absolute
path with `--icon-path`; there is no built-in icon-name registry.

After editing the Dockerfile, summarize both the durable change and anything
you mirrored into the live container. Ask the user to use Outer Shell's
**Rebuild** action only when the live result could not reasonably be produced.
Do not claim a newly declared app or command is available until it has been
published in the running container or by a rebuild.

---

# Publishing container tools with `outerctl`

`outerctl image` records portable declarations in an OCI image. These commands
are intended for Dockerfile `RUN` instructions: they do not need a running
`outershelld`, and they do not start software during the image build. When the
container starts, Outer Shell's internal service manager reads the declarations
and publishes them to the server-level Outer Shell.

## Publish an app

```dockerfile
RUN outerctl image add-app \
    --id ID \
    --name "Display Name" \
    [--url /initial/path] \
    [--working-directory /absolute/path] \
    [--icon-path /absolute/path/icon.png] \
    (--socket-argument=OPTION | --tcp-port PORT) \
    -- /absolute/path/to/server [ARGUMENTS...]
```

Use exactly one transport:

- `--socket-argument=OPTION` starts the executable with an additional
  `OPTION=/run/user/0/org.outershell.image.ID` argument. Use this when the
  server can listen directly on a Unix socket.
- `--tcp-port PORT` starts the declared server and publishes its loopback TCP
  port through a Unix socket. The image must provide `bash` and `socat`.

The service starts eagerly with the container and is restarted after failure.
Its logs are exposed through Outer Shell. `--url` defaults to `/`, and
`--working-directory` defaults to `/root`.

Example:

```dockerfile
RUN outerctl image add-app \
    --id jupyter-work \
    --name "JupyterLab: work" \
    --url /lab \
    --working-directory /work \
    --socket-argument=--ServerApp.sock \
    -- /opt/venvs/default/bin/jupyter-lab \
        --allow-root \
        --no-browser \
        --ServerApp.sock_mode=0600 \
        --IdentityProvider.token= \
        --ServerApp.password= \
        --ServerApp.root_dir=/work
```

## Publish a command-line tool

```dockerfile
RUN outerctl image add-command \
    --id ID \
    --name "Display Name" \
    [--working-directory /absolute/path] \
    [--user USER] \
    [--icon-path /absolute/path/icon.png] \
    -- /absolute/path/to/executable [ARGUMENTS...]
```

Outer Shell displays the declaration under **COMMAND LINE TOOLS**. Selecting it
copies a host command that opens the tool interactively inside this container.
The working directory defaults to `/root`, and the user defaults to `root`.

Example:

```dockerfile
COPY codex-cli.png /usr/local/share/outershell/icons/codex-cli.png

RUN outerctl image add-command \
    --id codex \
    --name "Codex CLI" \
    --working-directory /work \
    --user root \
    --icon-path /usr/local/share/outershell/icons/codex-cli.png \
    -- /usr/local/bin/codex --dangerously-bypass-approvals-and-sandbox
```

## Rules and inspection

- IDs contain 1–48 letters, digits, periods, underscores, or hyphens.
- Executables, working directories, and icon paths are absolute paths inside
  the image.
- Icon files must already exist when `outerctl image` runs. Common bitmap
  formats are supported; keep the file at or below 512 KB.
- Use a different stable ID for each distinct launcher.
- Add declarations to the Dockerfile. Running them only in a live container is
  not a reproducible configuration and does not replace an Outer Shell rebuild.
- `outerctl image list-commands` prints the command declarations in the current
  image for machine consumption.

The installed `outerctl` prints its usage synopsis when an invalid declaration
is rejected. Treat that binary as authoritative if it differs from this file.

## Local instructions

OpenCode container installations bundle
`/usr/local/share/outershell/container-agent.md` and
`/usr/local/share/outershell/OUTERCTL.md` for automatic context and offline use. This page is generated from the same source files.
