Caddy is the simplest TLS path — automatic HTTPS from a generated `Caddyfile`, no
certificate management by hand. Right choice for a single app instance without `--scale`.

<Callout title="The one rule">
  Leave `images.proxy` unset until you need HTTPS at the edge or `--scale app=N`. Once set, `app`
  stops publishing its host port directly — Caddy (or Traefik) does.
</Callout>

## Quick start

<Steps>

<Step>
### Pin the proxy

```typescript title="oke.config.ts"
images: {
  proxy: "caddy:2-alpine",
},
```

</Step>

<Step>
### Set the public hostname

```bash title=".env.local"
OKE_PROXY_HOST=app.example.com
```

Unset defaults to `localhost` — Caddy issues a **local** TLS cert instead of Let's Encrypt.

</Step>

<Step>
### Include the proxy layer

```bash
docker compose -f docker-compose.yml up -d
```

Generated `Caddyfile`: `{$OKE_PROXY_HOST:localhost} { reverse_proxy app:6530 }`.

</Step>

</Steps>

## Required env

| Variable                | Required?   | Meaning                                                                     |
| ----------------------- | ----------- | --------------------------------------------------------------------------- |
| `OKE_PROXY_HOST`        | Recommended | Public hostname for ACME; default `localhost` → local TLS only              |
| `allowedHosts` (config) | Production  | Must include the public hostname — see [Security](/docs/reference/security) |

Caddy has no separate ACME email env in this recipe (unlike Traefik's
`OKE_PROXY_ACME_EMAIL`).

## Data and backup

| Volume         | Path                                      | What it stores                  |
| -------------- | ----------------------------------------- | ------------------------------- |
| `proxy-data`   | `/data`                                   | ACME certificates, account keys |
| `proxy-config` | `/config`                                 | Caddy runtime config            |
| Bind mount     | `./Caddyfile` → `/etc/caddy/Caddyfile:ro` | Generated site block            |

**Backup means:** preserve the `proxy-data` named volume (and ideally `proxy-config`) so
Let's Encrypt rate limits and cert renewals survive recreates. Losing `/data` forces
re-issuance. The `Caddyfile` is regenerated by `oke docker`.

## Production note

Caddy has **no** service-discovery story for multiple `app` replicas. Once you run
`docker compose up --scale app=N`, switch to [Traefik](/docs/recipes/traefik) — it
discovers replicas from Docker labels instead of a static `reverse_proxy` target.

Also set `allowedHosts` to your public hostname before exposing the edge — see
[Security](/docs/reference/security).

## What the recipe configures

| Field          | Value                                        |
| -------------- | -------------------------------------------- |
| Ports          | `80` + `443` published; `app` stays internal |
| Healthcheck    | `caddy version`, every 10s, 5 retries        |
| Connection URL | `https://<host>`                             |

## Troubleshooting

<Accordions>
<Accordion title="ACME fails — connection refused / challenge timeout">

`OKE_PROXY_HOST` must be a DNS name that resolves to this host on ports 80/443. Localhost
never gets a public Let's Encrypt cert — that path uses Caddy's local CA. Check firewall
and that nothing else binds `:80`.

</Accordion>
<Accordion title="Browser trust errors on localhost">

Expected with the local CA. Install Caddy's local root for that machine, or set a real
`OKE_PROXY_HOST` with public DNS when you need a public cert.

</Accordion>
</Accordions>

## Learn more

- [Traefik](/docs/recipes/traefik) — multi-replica discovery via Docker labels
- [nginx](/docs/recipes/nginx) — static HTTP reverse proxy
- [Security](/docs/reference/security) — `allowedHosts`

## Next

<Cards>
  <Card title="Traefik" description="Multi-replica alternative." href="/docs/recipes/traefik" />
  <Card title="nginx" description="Static HTTP reverse proxy." href="/docs/recipes/nginx" />
  <Card
    title="PgDog"
    description="Pool Postgres behind the same stack."
    href="/docs/recipes/pgdog"
  />
</Cards>
