Installing DBSheriff in a public or a private subnet, and opening the consoles

DBSheriff is one Linux host that runs the whole stack. Two questions decide how you place it on your network:

  1. How does the host get the product, and how does it reach your databases? (outbound)
  2. How do your people reach the two consoles? (inbound)

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.

1. Choose your layout

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.

2. What the host needs, in every layout

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).

3. A host in a public subnet

  1. Create the host in a public subnet with a public IP address (an Elastic IP on AWS, a static external address on GCP, a static public IP on Azure, so the address survives a restart).
  2. Set the firewall (security group, VPC firewall rule or network security group). Allow inbound 443 from your office and VPN ranges, and 22 from your administrator address, and nothing else. Do not allow 8501, 8502 or 3000 from the internet.
  3. Install with the bundled HTTPS front door and an allow-list. Replace the name with the DNS name users will type, or the public IP:

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.

4. A host in a private subnet

The host has no public address. Two things replace it: a way out to fetch the product, and a way in for your people.

4.1 The way out

Pick one:

The first install needs the way out. After that the host needs it mainly for upgrades and for databases in other networks.

4.2 Install

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

4.3 The way in: pick how people reach the consoles

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.

4.4 Certificates on a private host

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

4.5 A private host that cannot reach the internet at all

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.

5. Opening the consoles: the setting behind it

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.

6. Trusting the internal certificate authority

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).

7. Reaching your databases from a private subnet

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.

8. Check the whole thing

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.