DBSheriff is one Linux host that runs the whole stack. Two questions decide how you place it on your network:
The installer is the same in every case. What changes is the network around the host and the one setting that opens the consoles to your users, ACCESS_MODE. This guide walks through a host in a public subnet (it has a public IP address), a host in a private subnet (it has none), and a private host with no internet at all.
Whichever you choose, the consoles are built for a private network. No access mode exposes them to the internet by itself, and none of the steps below should: where a public address exists, you restrict it to your own office and VPN addresses.
| Public subnet | Private subnet | Private subnet, no internet | |
|---|---|---|---|
| The host has | A public IP address | Only a private address | Only a private address |
| It fetches the product by | Its direct internet route | A NAT gateway or a forward proxy (private endpoints cover only the cloud's own services) | The offline bundle you copy to it |
| Your users reach the consoles by | HTTPS to the public address, allowed only from your networks | A VPN, a cloud port-forward (SSM, IAP, Bastion), an SSH tunnel or an internal load balancer | The same |
| Install mode | Connected | Connected | Air-gapped (--airgap) |
| Best when | A small team, a proof of concept, nothing else to build | Production; the databases are in private subnets too | Regulated or isolated networks |
If you are unsure, take the private subnet: it removes the public address as something to defend, and the extra step is only how people connect.
The machine. Linux x86_64 (Ubuntu 22.04+, RHEL or Rocky 9+, Debian 12+, Amazon Linux 2023) with Docker Engine 24+ and the Compose v2 plugin, 4 vCPU and 32 GB of memory (the minimum tier) and at least 100 GB of disk for the images and the system, plus room for the audit trail: about 54 GB per sustained scored event per second at the default 90-day retention. The installation guide (inside the package) has the sizing table, the Docker steps and the host preparation (swap, journal, OOM protection).
Network, outbound (from the host):
| To | Port | Why |
|---|---|---|
get.dbsheriff.ai, releases.dbsheriff.ai |
443 | The installer and the signed release (connected install only) |
ghcr.io and Docker Hub |
443 | The product images and the third-party images (connected install only) |
tuf-repo-cdn.sigstore.dev |
443 | The trust root that verifies the signatures (connected install only) |
| Every database DBSheriff monitors | Its normal port (5432, 3306, 1433, 1521, 39015, 5439, 27017) | Reading statements and, if you turn it on, responding |
| Snowflake, BigQuery, Databricks, Spanner | 443 | Their APIs (no host to place in a subnet) |
An air-gapped install needs none of the first three rows.
Network, inbound (to the host), only what your layout uses:
| Port | What | Open it to |
|---|---|---|
| 443 | The HTTPS front door (caddy mode, the default) |
Your users' networks only |
| 80 | Redirects to 443; needed from the internet only for the acme certificate type |
Your users' networks, or nothing |
| 8501, 8502 | Reporting and Admin Console in plain HTTP (internal and proxy modes) |
Your VPN range or your load balancer only |
| 3000 | Grafana (internal, proxy) |
Your VPN range only |
| 22 | SSH for you | Your own address only; or nothing, if you use SSM, IAP or Bastion |
Kafka, Kafka UI, the training database and the language model listen on the host's loopback address only; there is nothing to open for them. Docker's published ports bypass ufw and firewalld, so the host firewall alone is not enough: the cloud security group or network rule is the control that counts (installation guide, section 4.7).
bash
sudo bash install-v1.4.0.sh --version v1.4.0 --token '<download token>' -- \
--access-mode caddy --hostname dbsheriff.acme.example
The installer asks for the certificate source (below) and for an optional allow-list of client networks. Answer with your office and VPN ranges, for example 203.0.113.0/24 198.51.100.17/32. It is stored as CADDY_ALLOW_CIDRS in /opt/dbsheriff/state/.env, and every address outside it gets a 403 from the front door itself, on top of the cloud firewall.
4. Choose the certificate. With a public DNS name that points at the host, acme gets a free Let's Encrypt certificate (it needs port 80 reachable from the internet and an e-mail address). With your own certificate, use file. With neither, internal uses Caddy's own CA: traffic is encrypted at once and browsers warn until you trust the CA (section 6).
5. Check it from a machine inside an allowed range:
bash
curl -k https://dbsheriff.acme.example/healthz # {"status":"ok", ...} Reporting Console
curl -k https://dbsheriff.acme.example/admin/healthz # Admin Console
Then open https://dbsheriff.acme.example/ (Reporting Console) and https://dbsheriff.acme.example/admin/ (Admin Console) in a browser and sign in with the user name and passwords the installer printed.
If you can, put a VPN or an identity-aware proxy in front instead of exposing 443 to a public range. The consoles' own login throttling, lockout and session checks apply either way, but a login page on the internet is a target.
The host has no public address. Two things replace it: a way out to fetch the product, and a way in for your people.
Pick one:
curl at it with the HTTPS_PROXY environment variable, set the proxy in Docker's own configuration for the image pulls, and allow the hosts in section 2.ghcr.io, Docker Hub or get.dbsheriff.ai; for those you still need the NAT or the proxy, or the air-gapped install below.The first install needs the way out. After that the host needs it mainly for upgrades and for databases in other networks.
Exactly as in the installation guide (inside the package), section 4.4, with the access mode you choose in section 4.3. For example, a private host that your users reach over a VPN, with HTTPS:
sudo bash install-v1.4.0.sh --version v1.4.0 --token '<download token>' -- \
--access-mode caddy --hostname dbsheriff.corp.local
| You have | Use | ACCESS_MODE |
Users type |
|---|---|---|---|
| A company VPN, Direct Connect, ExpressRoute, a site-to-site link | The VPN, with the bundled HTTPS front door | caddy |
https://dbsheriff.corp.local/ |
| A VPN you do not want to terminate TLS on | The VPN, plain HTTP inside it | internal |
http://<host-ip>:8501/ and :8502/ |
| No VPN, an AWS host | A Session Manager port forward | internal (forward 8502 and 8501) or caddy (forward 443) |
http://localhost:8502/ with internal; with caddy, see below the commands |
| No VPN, a GCP host | An IAP TCP tunnel | internal (tunnel 8502 and 8501) or caddy (tunnel 443) |
http://localhost:8502/ with internal; with caddy, see below the commands |
| No VPN, an Azure host | Azure Bastion native tunnel | internal (tunnel 8502 and 8501) or caddy (tunnel 443) |
http://localhost:8502/ with internal; with caddy, see below the commands |
| An SSH bastion | An SSH tunnel | internal |
http://localhost:8502/ |
| A load balancer or reverse proxy you already run | Your ingress in front of the consoles | proxy |
Your own hostname |
internal mode sends the login and the session over plain HTTP, so use it only inside a VPN or a tunnel, and choose it on purpose (--access-mode internal; an unattended --yes run without a choice installs caddy).
Port forwards (run on the person's own machine, one terminal per console; each stays open while they work):
# AWS, Session Manager (AWS CLI v2 and the Session Manager plugin installed, ssm:StartSession allowed)
aws ssm start-session --target i-0123456789abcdef0 --document-name AWS-StartPortForwardingSession \
--parameters portNumber=8502,localPortNumber=8502 # Admin Console -> http://localhost:8502
aws ssm start-session --target i-0123456789abcdef0 --document-name AWS-StartPortForwardingSession \
--parameters portNumber=8501,localPortNumber=8501 # Reporting UI -> http://localhost:8501
# Google Cloud, IAP (the firewall must allow 35.235.240.0/20 to the port)
gcloud compute start-iap-tunnel dbsheriff-host 8502 --local-host-port=localhost:8502 --zone=<zone>
# Azure, Bastion (Standard SKU, native client support on)
az network bastion tunnel --name <bastion> --resource-group <rg> --target-resource-id <vm resource id> \
--resource-port 8502 --port 8502
# Any bastion with SSH
ssh -L 8501:localhost:8501 -L 8502:localhost:8502 <user>@<host>
Anyone who may open these tunnels can also open a shell on the host, so treat that permission like a key to it.
Through a tunnel, internal mode is the simple choice: the tunnel encrypts the hop, and the consoles are at http://localhost:8501/ and http://localhost:8502/. In caddy mode forward port 443 (for example portNumber=443,localPortNumber=8443) and browse by the name the front door was installed with, not by localhost: the front door serves a site per name in CADDY_HOSTNAME, so add a line such as 127.0.0.1 dbsheriff.corp.local to the hosts file of the person's machine and open https://dbsheriff.corp.local:8443/ (Reporting Console) and https://dbsheriff.corp.local:8443/admin/ (Admin Console). Port 8502 in caddy mode is not usable directly, because the Admin Console then expects its /admin prefix to be stripped by the front door.
CADDY_TLS |
Works on a private host? |
|---|---|
internal |
Yes. Nothing to arrange, and browsers warn until the CA is trusted (section 6) |
file |
Yes. Your own certificate and key, as absolute paths on the host |
acme-dns |
Yes. Let's Encrypt proves the name by a DNS record, so the host stays private. Needs a DNS provider token (Cloudflare) or Route 53 credentials |
acme |
No. Let's Encrypt must reach port 80 from the internet |
Use the air-gapped install: download the customer package, the image bundle (about 13 GB) and the signed checksums on a connected machine, verify them, copy them to the host, and run install.sh --airgap <bundle>. The steps and the exact commands are in the installation guide (inside the package), section 4.6, and the published install page ("Install (air-gapped host)"). The host then needs no outbound access except to your databases, with one exception: the every-minute container health check (it fills the System Health tab and sends the alert mails) runs in a small Python environment that the installer builds on the host from a Python package index or your own mirror (and, on Debian or Ubuntu without python3-venv, an apt source). An air-gapped host with neither finishes the install with "NOT scheduled: container-health"; the rest of DBSheriff runs normally, and stack.sh health shows the same information on demand.
The consoles are running as soon as the install finishes. What you choose is who can reach them. Everything is the ACCESS_MODE setting in /opt/dbsheriff/state/.env:
ACCESS_MODE |
What listens | TLS |
|---|---|---|
caddy (the default) |
The bundled front door on 443: Reporting Console at /, Admin Console at /admin/ |
Yes |
internal |
The consoles on the host: :8501 Reporting, :8502 Admin |
No |
proxy |
The same ports, behind your reverse proxy or load balancer; set TRUSTED_PROXY_IPS to the proxy's exact addresses |
At your proxy |
To change the mode later, re-run the installer with the new mode, for example sudo /opt/dbsheriff/current/deploy/onprem/install.sh --reconfigure --access-mode internal. It sets the bind addresses and the other settings the mode needs, and it recreates the consoles. Editing ACCESS_MODE in .env and running stack.sh up is not enough, because those other settings stay as they were. Leaving caddy does not stop the bundled front door: stop it yourself with docker stop dbsheriff-<env>-caddy-tls (<env> is ENV_NAME in .env), and leave ADMIN_BASE_PATH blank in .env for internal (and for proxy, unless your proxy serves the Admin Console under a path), because the installer sets it to /admin for caddy and does not clear it. Then check with sudo /opt/dbsheriff/current/deploy/onprem/stack.sh health. People sign in again afterwards. The full reference, with every variable, the certificate types and the login rate limits, is docs/console-access.md in your release package; the single sign-on settings are in docs/sso.md.
For a load balancer in front, point it at http://<host>:8501/ (Reporting, served at the root of its hostname) and http://<host>:8502/ (Admin, at the root of its own hostname or under a path prefix), with the health check GET /healthz. The live feed uses server-sent events on /stream, so turn response buffering off for that path and allow idle connections of a few minutes.
With CADDY_TLS=internal, export the front door's root certificate once and install it on your users' machines (or push it by MDM or group policy):
docker cp dbsheriff-<env>-caddy-tls:/data/caddy/pki/authorities/local/root.crt .
<env> is the environment name you gave the installer (prod by default).
The host needs a network path to each database's port: a route (the same VPC or VNet, peering, a transit gateway or a VPN), a security group or firewall rule that allows the DBSheriff host's address on the database's port, and for a managed database that is in another cloud or reachable only from certain addresses, the NAT gateway's fixed public address in that database's allow-list. Then create the accounts and register the server: first login quick start, steps 3 to 5.
sudo /opt/dbsheriff/current/deploy/onprem/stack.sh health # exit 0 when everything is healthy
curl -k https://<name-or-ip>/healthz # caddy mode, from an allowed address
curl http://<host-ip>:8501/healthz # internal mode, over the VPN
If a person cannot open a console, the usual causes in order are: the security group or firewall (and remember Docker bypasses ufw), the allow-list CADDY_ALLOW_CIDRS answering 403 to their address, a certificate their browser does not trust (section 6), or a tunnel that is not running. stack.sh logs caddy-tls shows what the front door decided. If the page loads but the live feed stays empty behind a proxy, response buffering on /stream is the first thing to check.
Questions, or something in this guide doesn't match what you see: SECURITY.md
in your release package, or support@dbsheriff.ai.