# Running a service in a container

**Not built.** This page is the design; Jadpo doesn't do this yet. See [What is built](/docs/external-services-built.md).

A provider's API can be a stateless container image that Jadpo starts beside
your app, such as Gotenberg turning HTML into PDFs or ClamAV scanning uploads.
This is called a *service run by Jadpo*, and it isn't built yet. Its package
declares the image with `runs:`, and Jadpo starts and supervises the container
through Docker or Podman and hands the connection the address. This `runs:`
isn't the one on a deployable, which
[lists its background work](/docs/declared-deployables.md#runs-the-background-work-it-runs).

Here is the design for Gotenberg's package and a connection to it, which fills
the `documents` capability. It doesn't compile today:

```jadpo
// packages/gotenberg/v8/provider.jadpo
/// Gotenberg's HTTP API, run by Jadpo as a container beside the app.
provider {
    protocol: Protocol.multipart_http
    runs: {
        image: "gotenberg/gotenberg:8@sha256:…"
        port: 3000
        asks: ["fonts.googleapis.com"]
    }
}

// app.jadpo
application {
    connections: {
        pdfs: {
            package: Gotenberg.v8
            domains: [billing]
            outbound: ["fonts.googleapis.com"]
        }
    }
    documents: {
        connection: pdfs
    }
}
```

| `jadpo dev` | `jadpo test` | Native deployable | Workers deployable |
|---|---|---|---|
| Started as a container by `jadpo dev` | Never started: the package's fake answers | A container beside the app: `jadpo build` writes the configuration that runs it (Compose first), then the deployable supervises it itself | None: Workers runs no containers, so the connection gives `host:` where the service is hosted elsewhere |

No built-in service starts a container, but one could use a service run by
Jadpo through its capability, as documents would use Gotenberg.

## Services

- **Only stateless services.** A service holds no data and has no volume,
  and each app instance runs its own copy, so many servers need nothing
  shared. Anything that holds data (PostgreSQL, Redis) is infrastructure
  the operator provides and the app reaches through a driver connection.
- **A service never starts until approved.** Installing shows the image, its
  digest and the hosts it asks for; the app names it on a connection, and a
  new or changed image needs a person's acceptance in the approval report
  before it runs, so an upgrade cannot swap in a different image unseen. The
  image's digest is written in the package source, which the package's own
  digest pins; an own integration in `shared/providers/` has no lock entry,
  and its image is listed the same way.
- **Outbound network needs the app's permission.** A package only asks
  (`asks:`); the service gets none of those hosts until the connection
  grants them (`outbound: [...]`), and a new grant is a new outside host,
  which needs a person's acceptance (`NetworkChange.host_added`). Until
  then `jadpo check` names the hosts asked for and the line that grants
  them.
- **Three settings Jadpo always applies** when it creates a container: no
  outbound network unless granted, never privileged, and no access to the
  host's disk. Jadpo creates the containers itself, so there is no
  configuration to audit.
- **Reaching a service.** A package says only the port its service listens
  on inside its container, so two packages can both use 3000. Jadpo gives
  each app a private network that only it and its services are on, and
  hands each connection the address: the service's name on that network
  when the app runs in a container, or a loopback port the engine picked
  when it runs as a plain process. Nobody writes an address.
- **Jadpo is a client, never a container runtime.** It talks to whatever
  engine the machine has through the Docker-compatible API, which Docker and
  Podman both serve, and leaves pulling, isolation and restart policies to
  it. An app with no service never needs an engine; when one is needed and
  none is found, `jadpo dev` and the deployable say so and how to install
  one.
- **Docker stays behind the scenes.** `jadpo services`
  lists each service (package, image, state, health, CPU and memory,
  restarts, uptime); `jadpo services start`, `stop` and `restart <name>`,
  `logs <name>` (`--follow`, `--since`) and `shell <name>` (development
  only) manage them, each with `--json`. In production the commands talk to the running app,
  which supervises its services. A service's output joins the app's
  OpenTelemetry logs under its name, and a call to it appears in the
  request's trace as a provider call. You never write a Dockerfile or a
  Compose file.
- **Where services run.** `jadpo dev` starts the declared services, and
  tests keep using fakes. `jadpo build` writes the configuration that runs
  each one beside the app, Compose first. The native deployable supervises
  its services itself. On Workers, which runs no containers, a service is
  hosted elsewhere and the connection gives `host:`.

## Not built yet

Jadpo starts no containers yet, and `jadpo services` isn't a command. Until it
does, run the container yourself, with Docker, Podman or Compose, and connect
to it as you would to any API at an address you know:

1. Start the image yourself, with its port open on the machine your app runs
   on, and keep it running and up to date yourself.
2. Describe its API in `shared/providers/`, with neither `hosts:` nor `runs:`
   in `provider.jadpo`.
3. Give the connection `host:`, read from a `Url` setting such as
   `http://localhost:3000` ([Connecting in `app.jadpo`](/docs/connecting-in-app-jadpo.md)).

Calls go over HTTPS, or plain HTTP only to the machine the app is on. So on
Workers, or when the container is on another machine, it needs an HTTPS
address. Your actions call its operations through the connection: the
`documents` capability isn't built either.
