Install on your own server
For the person who runs servers at your company. This guide runs the Portward gateway with Docker Compose on a Linux machine in your own network, in about 30 minutes. On AWS, Azure or Google Cloud, a template sets up the same gateway for you: see Pick a cloud.
Start with Get started, steps 1 to 3: by then you have your license key (pwlic_…) and your
gateway’s address.
What you need
Section titled “What you need”- A small Linux server with Docker, in the network that can reach your databases (1 CPU and 1 GB of memory is enough to start). A cloud VM is fine.
- An address for the gateway. By default it is
https://<name>.gw.portward.ai, which reaches the server through the Portward relay with no inbound ports. Or use a name of your own, such asportward.yourcompany.com. Then ports 80 and 443 must be open to the internet, because Claude and ChatGPT connect from their own servers. The rest of this guide writesportward.yourcompany.comfor your address. - Your company directory: Microsoft Entra, Okta, Google Workspace, or any OpenID Connect directory. Without one, a sign-in provider such as WorkOS AuthKit works instead (step 1b).
- Read-only logins for the databases you’ll connect. Portward refuses a login that can write.
1. Set up sign-in with your directory (10 minutes)
Section titled “1. Set up sign-in with your directory (10 minutes)”Register one app for the gateway in your directory, with redirect address
https://portward.yourcompany.com/auth/callback. Sign-in setup has the steps for Entra, Okta and
Google.
Keep its issuer, client id and secret, and the admin group’s id or name, for step 2.
1b. Or with WorkOS, without a directory (5 minutes)
Section titled “1b. Or with WorkOS, without a directory (5 minutes)”Set up WorkOS AuthKit as Sign-in setup describes, and keep your AuthKit domain for step 2.
2. Create the install files (2 minutes)
Section titled “2. Create the install files (2 minutes)”Run one gateway per license key. Don’t start a second install folder with the same key.
On the server, create an empty folder with a name no other install on it uses, and go into it. Docker Compose names the containers and volumes after the folder, so two installs in folders of the same name would share them. Put your license key in a variable, so it isn’t typed into a command line. Then sign in to Portward’s image registry with it and run the installer. With Okta, read “With Okta” below first.
read -rs PORTWARD_LICENSE && export PORTWARD_LICENSE # paste the key, then Enterprintf %s "$PORTWARD_LICENSE" | docker login registry.portward.ai -u portward --password-stdindocker run --rm -e PORTWARD_LICENSE --user "$(id -u):$(id -g)" -v "$PWD:/install" -w /install \ registry.portward.ai/portward/gateway:1.1.0 init --directory <issuer> --client-id <client id> --admin-group "<group>"(With WorkOS instead: --issuer https://your-team.authkit.app in place of --directory, --client-id and
--admin-group.)
With Okta, init in 1.1.0 refuses Okta’s org server (https://<your org>.okta.com) with “doesn’t publish a plain
https jwks_uri”. Give it https://<your org>.okta.com/oauth2/default as <issuer> instead. Then open
portward.toml in a text editor. Under [auth], set directory to Okta’s org server, as
Sign-in setup uses it, and add a scopes line right under it, because Okta sends groups only when asked for them. Leave the comment lines
as they are:
directory = "https://<your org>.okta.com"scopes = ["groups"]With a directory, the app’s secret goes in .env, on the empty PORTWARD_DIRECTORY_SECRET= line init wrote.
To put it there without typing it into a command line:
read -rs SECRET # paste the app's client secret, then Enter(umask 077; grep -v '^PORTWARD_DIRECTORY_SECRET=' .env > .env.new && \ printf 'PORTWARD_DIRECTORY_SECRET=%s\n' "$SECRET" >> .env.new) && mv .env.new .envThe installer writes the files below, plus relay.toml and relay.env (your license key, owner-only) for the relay
client. To use your own address instead of the relay, add --domain portward.yourcompany.com (then point it at the
server and open ports 80 and 443, step 3). It ends with a short list of next steps, which steps 3 to 6 below follow.
With a directory, 1.1.0’s list says “add both addresses above” without listing them: they are the admin connector
(step 4) and the data connector (step 6). Where the list or portward.toml names docs/sign-in.md or
docs/install.md, those are Sign-in setup and this page.
| File | What it is |
|---|---|
portward.toml | The gateway’s settings. You’ll edit admin_subjects and setup_hosts here. |
.env | The gateway key, and with a directory the app’s secret. Back it up and keep it secret: saved connections can’t be opened without the key. |
compose.yaml | The gateway, plus Caddy, which gets and renews the HTTPS certificate on its own. |
Caddyfile | Caddy’s one-line setting for your address. |
No inbound ports: the Portward relay (the default)
Section titled “No inbound ports: the Portward relay (the default)”With the relay, skip the DNS and firewall steps below: the gateway connects out on port 443 and nothing opens inbound. The relay passes your traffic through still encrypted; the certificate stays on your server. To put in a new license key later, see Replace a license key.
3. Start it (5 minutes)
Section titled “3. Start it (5 minutes)”- With your own address only: point
portward.yourcompany.comat the server (a DNS A record) and open ports 80 and 443. (Through the relay, nothing to do.) docker compose up -ddocker compose exec gateway portward --config /config/portward.toml doctor
doctor checks, from outside the server, what Claude and ChatGPT need: the HTTPS certificate, the sign-in provider,
sign-in for both connectors, and the records. It says what to fix.
4. Make yourself the admin (3 minutes)
Section titled “4. Make yourself the admin (3 minutes)”- Add the admin connector,
https://portward.yourcompany.com/admin/mcp, in Claude or ChatGPT and sign in. Get started, step 7 shows where to add it. - Members of the group you gave as
--admin-groupare admins as soon as they sign in. If you have no admin group, as with WorkOS, the gateway refuses your first sign-in because no one is an admin yet. Its log names your sign-in id:docker compose logs gateway | grep "admin connector refused". - Without an admin group, put that id in
admin_subjectsinportward.toml, thendocker compose restart gateway, and connect again.
5. Connect your data (10 minutes)
Section titled “5. Connect your data (10 minutes)”- In
portward.toml, list the database hosts admins may connect insetup_hosts(for example["db.internal.yourcompany.com"], or"*.yourcompany.internal"for a whole domain), thendocker compose restart gateway.- Object stores on their cloud’s own endpoint (S3, Azure Blob, Cloud Storage), BigQuery, Microsoft 365 and Google Drive need no entry.
- Snowflake needs its account’s host, such as
youraccount.snowflakecomputing.com. - A host on a domain other customers share needs its exact name, never a wildcard: an RDS or Redshift endpoint
(
*.amazonaws.com, such aswg.123456789012.us-east-1.redshift-serverless.amazonaws.com) or a Databricks workspace (such asdbc-1234abcd-5678.cloud.databricks.com). - Redshift IAM sign-in without an access key uses the gateway’s own AWS role, which is off until you set
PORTWARD_REDSHIFT_GATEWAY_ROLE=1.
- Set up the connection from the admin connector and choose what the AI may see: Get started, step 8.
6. Give your team access
Section titled “6. Give your team access”-
Say who may use the data connector in
portward.toml, thendocker compose restart gateway.- With Entra or Okta, by group. Keep
users = []and list the groups inuser_groups. Don’t remove theusersline: without it, everyone your directory lets in can ask.Entra sends group Object IDs, Okta group names. The group must be assigned to the sign-in app and, with Okta, pass its groups claim filter (Sign-in setup, step 3). Entra doesn’t mark emails as verified, so an email domain doesn’t work there.users = []user_groups = ["Portward Users"] - With Google Workspace or WorkOS, by email.
A whole domain needs a verified email in the sign-in token. Google Workspace sends one. In WorkOS, addusers = ["@yourcompany.com"] # everyone whose verified email is at yourcompany.com# users = ["user_01ABC", "dana@yourcompany.com"] # or people one by one
emailandemail_verifiedto the access token’s JWT template; otherwise list people by id.
With
usersanduser_groupsboth empty, only admins can ask, which is enough for a first test. In 1.1.0, doctor’susersline counts only theusersentries, not the groups. Someone refused is named in the log:docker compose logs gateway | grep "data connector refused". - With Entra or Okta, by group. Keep
-
Everyone adds the data connector,
https://portward.yourcompany.com/mcp: Get started, step 9.
Running it
Section titled “Running it”- Update, back up and restore, and automatic updates: Update and back up.
Keep
.envapart from the data: it holds the gateway key, which backups need and don’t contain. - Check the records:
docker compose exec gateway portward --config /config/portward.toml records verifyconfirms no answer record, reveal (a Show of a hidden value) or setup change was altered or removed. - Logs:
docker compose logs gateway. They name people by their sign-in id and never contain row values. - After
docker compose restart gateway, the address answers 502 for about 2 seconds, until the gateway is up again.
If something’s wrong
Section titled “If something’s wrong”doctor says | Fix |
|---|---|
| no valid HTTPS certificate yet | DNS isn’t pointing at the server yet, or ports 80/443 are closed. docker compose logs https shows Caddy’s attempts. |
| didn’t answer | The containers aren’t running (docker compose ps), or a firewall blocks the server. |
| discovery … doesn’t name | public_url in portward.toml doesn’t match the address people use. |
| sign-in provider didn’t answer | The directory address (with WorkOS, issuer) in portward.toml is wrong, or the server can’t reach the internet. |
| nobody is an admin yet | Step 4. |
| anyone … signs in can use the data connector | Add users = [...] under [auth] (step 6). |
If you use WorkOS and Claude can’t connect even though doctor passes, check that both connector addresses are in
WorkOS’s Resource Indicators exactly, with https:// and no trailing slash.