# Getting started

> Drop GroundShade in behind Caddy, nginx, or HAProxy. Five minutes to a working request path.

<aside class="note" aria-label="Beta software notice">
  <p class="note-head">Beta · pre-1.0</p>
  <p>
    GroundShade is a young project. It runs in production on
    [kycnot.me](https://kycnot.me) and has CI gates for attack
    survival, but it's still sub-1.0. Expect occasional bugs and small
    config-schema changes between minor versions. If you hit something
    rough, open an issue on
    [Codeberg](https://codeberg.org/groundshade/groundshade/issues).
  </p>
</aside>

GroundShade sits between your TLS terminator and your app. It does
not terminate TLS itself; your fronting proxy keeps doing that and
forwards the JA4 fingerprint as `X-JA4`.

```
client → TLS terminator (Caddy / nginx / HAProxy) → groundshade → your origin
```

## Run with Docker Compose

```yaml
services:
  groundshade:
    image: codeberg.org/groundshade/groundshade:latest
    restart: unless-stopped
    environment:
      GROUNDSHADE_UPSTREAM_URL: http://your-app:3000
      GROUNDSHADE_ADMIN_TOKEN: ${GROUNDSHADE_ADMIN_TOKEN:?set an admin token}
      GROUNDSHADE_TRUSTED_PROXIES: 172.18.0.2/32
    ports:
      - "127.0.0.1:9090:9090"
    volumes:
      - groundshade-state:/var/lib/groundshade

volumes:
  groundshade-state:
```

Generate the admin token once:

```sh
openssl rand -hex 32
```

Put it in your `.env`. The token gates `/admin/*` and `/metrics`.
Startup refuses to bind admin on a non-loopback address without a
token.

Then point your fronting proxy at `groundshade:8080` instead of your
app. Note that, if you are using docker, groundshade and your app need to share the same docker network.

## Check the dashboard

The container's admin port is published to host loopback on
`127.0.0.1:9090`. Open it in a browser:

```
http://127.0.0.1:9090/
```

The login page accepts your admin token and sets a cookie. After login
you land on the dashboard.

If you're not on the host, tunnel over SSH:

```sh
ssh -L 9090:127.0.0.1:9090 your-host
```

## Forward JA4 (optional, recommended)

The JA4 arm of the rate signal needs your fronting proxy to forward
the TLS fingerprint as `X-JA4`. Without it you keep the per-IP/24 arm
and trustless persistence; you lose detection of botnets that share a
TLS library across many IPs.

Stock Caddy has no JA4 placeholder. The build recipe lives at
[`examples/deploy/caddy-ja4.Dockerfile`](https://codeberg.org/groundshade/groundshade/src/branch/main/examples/deploy/caddy-ja4.Dockerfile)
and the matching Caddyfile at
[`examples/deploy/caddy-ja4.Caddyfile`](https://codeberg.org/groundshade/groundshade/src/branch/main/examples/deploy/caddy-ja4.Caddyfile).
For nginx and HAProxy see
[`nginx-ja4.conf`](https://codeberg.org/groundshade/groundshade/src/branch/main/examples/deploy/nginx-ja4.conf)
and
[`haproxy-ja4.cfg`](https://codeberg.org/groundshade/groundshade/src/branch/main/examples/deploy/haproxy-ja4.cfg).

GroundShade auto-detects JA4. After 100 requests (or 60 seconds), if
no JA4 has arrived, it logs a single WARN and the dashboard's signals
row reads `JA4: not detected`. Everything else keeps working.

## Trusted proxies

`GROUNDSHADE_TRUSTED_PROXIES` should list only your fronting proxy's
pinned IP. Trusted peers get `X-Forwarded-For` and `X-Real-IP` honored
and bypass the per-IP connection cap. Pin tight: a `/16` would grant
cap bypass to every container on the bridge.

Defaults trust only loopback (`127.0.0.1/32`, `::1/128`).

## Where to go next

- [Operating GroundShade](/docs/operating) covers daily workflows,
  the dashboard, the metrics reference, and the tuning table.
- [Architecture](/docs/architecture) explains the request lifecycle
  and the three defense levels.
- [Deployment hardening](/docs/hardening) is the pre-deploy security
  checklist. Read it before publishing.
- [Behind Cloudflare](/docs/cloudflare) covers the orange-cloud
  recipe and what gets silenced.
- [Building from source](/docs/build) builds local Docker images and
  multi-arch releases.
- [FAQ](/docs/faq) lists the questions operators ask first.
