Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# The Docker Hub page has its own audience (operators deploying the
# image), hence its own README. Never fatal: a description that fails to
# sync must not fail a publish that already pushed the image.
- name: Sync the Docker Hub description
uses: peter-evans/dockerhub-description@v4
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ vars.DOCKERHUB_IMAGE || 'whitemuush/datashield' }}
short-description: "Self-hosted breach exposure monitoring for your employees' credentials"
readme-filepath: ./docker/README.hub.md
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ the protected `dockerhub` environment, so a publication waits for a manual
approval and only runs from `main`, `develop` or a `v*.*.*` tag. The
`DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets belong to that environment.
Set the `DOCKERHUB_IMAGE` repository variable to publish under a different name
than the default.
than the default. The Docker Hub page has its own README,
[`docker/README.hub.md`](docker/README.hub.md), written for operators deploying
the image rather than contributors; the same workflow keeps it in sync.

## Documentation

Expand Down
110 changes: 110 additions & 0 deletions docker/README.hub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# DataShield

Self-hosted service that tells a business whether its employees' data has
surfaced in known breaches, with severity-based alerting and a customizable
security dashboard.

Source, issues and full documentation: https://github.com/WhiteMuush/DataShield

## Tags

| Tag | Content |
| --- | --- |
| `latest` | The most recent release. |
| `1.2.3`, `1.2` | A specific release, and the moving minor line. |
| `edge` | Built manually from a branch. Not a release, do not run it in production. |

Images are `linux/amd64`. Each one carries the Next.js standalone server and
runs as an unprivileged user.

## Quick start

The app needs a PostgreSQL 16 database. The container applies pending
migrations on start, so a fresh database is ready without any extra step.

```yaml
services:
app:
image: whitemuush/datashield:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
# Replace pw with the password you set on the db service below.
DATABASE_URL: postgresql://datashield:pw@db:5432/datashield
BETTER_AUTH_SECRET: replace-with-openssl-rand-base64-32
BETTER_AUTH_URL: https://datashield.example.com
DIRECTORY_ENCRYPTION_KEY: replace-with-openssl-rand-base64-32
ports:
- "3000:3000"

db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: datashield
POSTGRES_PASSWORD: pw
POSTGRES_DB: datashield
volumes:
- datashield-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U datashield -d datashield"]
interval: 5s
timeout: 3s
retries: 20

volumes:
datashield-db-data:
```

```bash
docker compose up -d
```

The app answers on port 3000. Generate every secret with
`openssl rand -base64 32`; never reuse the placeholders above.

## Configuration

Required. The container refuses to start without a database URL, and the app
refuses to handle directory credentials without an encryption key.

| Variable | Purpose |
| --- | --- |
| `DATABASE_URL` | PostgreSQL connection string. |
| `BETTER_AUTH_SECRET` | Signs sessions. 32 random bytes. |
| `BETTER_AUTH_URL` | Public base URL of the deployment, used for auth callbacks. |
| `DIRECTORY_ENCRYPTION_KEY` | Encrypts directory connection secrets at rest. 32 characters minimum. |

Optional.

| Variable | Purpose |
| --- | --- |
| `HIBP_API_KEY` | Enables Have I Been Pwned breach lookups. Without it, no external lookup runs. |
| `CRON_SECRET` | Secures `POST /api/cron`. Without it the endpoint returns 503 and no scan is scheduled. |
| `RESEND_API_KEY`, `EMAIL_FROM` | Email alerts to company admins. Both are needed, otherwise alerts are skipped. |
| `DIRECTORY_ENCRYPTION_KEY_PREVIOUS` | Former encryption key, read during a key rotation. |
| `RUN_MIGRATIONS` | Set to `false` to skip `prisma migrate deploy` on start, when a separate job owns the schema. |

## Operating notes

**Migrations.** The entrypoint applies them before the server starts. Running
several replicas at once means several concurrent migration attempts: give the
schema to a single job and set `RUN_MIGRATIONS=false` on the replicas.

**Health.** `GET /api/health` returns 200 with `{"status":"ok","db":"up"}` when
the process is up and the database is reachable, 503 otherwise. It is
unauthenticated and exposes no data, so it is safe as a probe.

**Scheduled scans.** Nothing runs on a timer inside the container. Set
`CRON_SECRET`, then have an external scheduler call `POST /api/cron` with the
header `authorization: Bearer <CRON_SECRET>`.

**Reverse proxy.** The image serves plain HTTP on 3000. Terminate TLS in front
of it and make `BETTER_AUTH_URL` match the public HTTPS URL, otherwise the
authentication callbacks break.

## License

See the LICENSE file in the source repository.
Loading