# Architecture

What runs inside the Ceryx container, how a request moves through it, and where data is stored.

## One container

Ceryx is a single container, with no other services to run next to it. Inside it:

- **NGINX** accepts HTTP/S requests, as a single worker process.
- **Ceryx Lua Engine** runs embedded in NGINX with LuaJIT in three parts:
  - **Routing** handles HTTP and HTTPS requests on ports 80 and 443, and finds each request's target, if any.
  - **Certificates** issues a new HTTPS certificate when needed, or selects an existing one.
  - **API** serves the [API](/docs/api) on port 8080, and writes routes and certificates to the database.
- **SQLite** keeps routes, their settings and certificates in one file, `/var/lib/ceryx/db.sqlite`, memory-mapped for reading.

![Ceryx architecture. Inside the Ceryx container, NGINX accepts HTTP and HTTPS requests on ports 80 and 443 and API calls on port 8080. The Ceryx Lua Engine runs in NGINX and has three parts: Routing, Certificates, and API. The API writes routes and certificates to memory-mapped SQLite; Routing reads routes and sends requests to services.](/images/docs/architecture.svg)

> [!NOTE] Route changes take effect immediately
> The API writes routes to the database, and Routing reads them from it. Both run in the same NGINX process, so when a route changes, the API also clears it from Routing's cache. The next request uses the new route without reloading NGINX.

## How a request moves through Ceryx

The Ceryx Lua Engine runs at two points in each request. [How routing works](/docs/concepts/routing) follows the same path from the point of view of your routes.

1. **During the TLS handshake** (HTTPS only), Ceryx reads the hostname the client asked for (SNI) and serves a certificate for it: one stored in the database, one from files named in the route's settings, a new one from Let's Encrypt, or the default certificate. If the route requires client certificates, Ceryx asks the client for one. See [How certificates work](/docs/concepts/certificates).
2. **Before proxying**, Ceryx checks the client certificate if the route requires one, redirects plain HTTP to HTTPS if the route enforces it, and looks up the route for the request's `Host` header.
3. **NGINX proxies the request** to the route's target over HTTP/1.1, and passes WebSocket upgrades through. It looks up the target's hostname with Docker's DNS server, or the one set in `CERYX_DNS_RESOLVER`. For routes in `redirect` mode, Ceryx answers with a redirect instead.

If there's no route for the hostname and no catch-all route, Ceryx answers `503 Service Unavailable` with its own error page.

## Where data lives

- **Routes and certificates** are in one SQLite database, at `CERYX_SQLITE_PATH`. That includes the certificates you store through the API and the ones Let's Encrypt issues. Ceryx creates the database itself.
- **Reads are memory-mapped.** SQLite maps the database file into memory, up to `CERYX_SQLITE_MMAP_SIZE` (1 GiB by default), so the proxy reads routes and certificates through memory instead of reading the file for each lookup. SQLite's write-ahead log keeps those reads from waiting on API writes. See [Storage settings](/docs/settings#storage).
- **Everything Ceryx keeps** is under `/var/lib/ceryx`, so keep that directory on a volume. The [production setup](/docs/deploy) does this with the `ceryx_data` volume.
- **[Settings](/docs/settings)** are environment variables. When the container starts, it writes NGINX's configuration from them and then starts NGINX, so a changed setting applies once you restart the container.

## How changes take effect

- **Routes.** Ceryx keeps the routes it looks up in NGINX's shared memory, so most requests don't read the database. Changing a route through the API clears its hostname from memory, so the next request uses the new route. The one exception is the catch-all route: hostnames it serves can briefly keep its previous target after you change it.
- **Certificates.** Ceryx reads certificates from the database for each new HTTPS connection, so a certificate you store applies to the next connection.
- **Settings** apply when the container restarts, as above.
