Deployment guide

Bifrost

Overview

Bifrost is a high-performance LLM gateway from Maxim AI. It sits between your applications and the model providers you use, and it exposes one OpenAI-compatible endpoint that fronts OpenAI, Anthropic, Google, Azure OpenAI, AWS Bedrock, Vertex, Groq, Mistral, Ollama and many more. This guide covers deploying and configuring the SecureAnyCloud Bifrost image on Ubuntu 24.04 on Azure.

Because the endpoint is OpenAI-compatible, existing application code usually needs only a base URL change. Behind that single endpoint, Bifrost provides automatic failover between providers and keys, weighted load balancing, per-key budgets and rate limits, semantic caching, MCP tool routing, and a full request log with cost and latency for every call.

Bifrost is a router, not a model host. It relays requests to providers using your API keys, so it needs no GPU and runs comfortably on a small CPU-only VM. It is a single statically linked Go binary with the web dashboard compiled into it. For that reason, this appliance needs no container runtime, no language runtime and no database server.

The SecureAnyCloud image ships the free and open source, Apache-2.0 licensed Bifrost as the official upstream release binary, pinned by content hash and tied to an exact upstream commit. No model provider key is baked into the image, so you add your own. A unique dashboard password and a unique virtual key are generated for each VM on first boot. The gateway in front of Bifrost does not start until first boot has proved that an unauthenticated request is answered with 401.

Bifrost is an independent open source project. SecureAnyCloud produces this image, which is not affiliated with, endorsed by, or sponsored by Maxim AI. The image ships the free and open source Apache-2.0 licensed software, unmodified.

What you get:

  • Bifrost v1.6.6, the official upstream linux/amd64 release binary, pinned by sha256 content hash rather than by a moving tag
  • Verified provenance: Go stamps the source commit into the binary, and this appliance asserts that the commit is a15edc24a8656950a10c6c3c60430b913cfb313b (upstream tag transports/v1.6.6), built from an unmodified tree
  • The Bifrost web dashboard, compiled into the same binary
  • An nginx access gateway on port 80, installed as a host package so that unattended security upgrades keep the internet-facing component patched
  • Embedded SQLite configuration and log stores, with no database server of any kind on the instance
  • A unique dashboard password and a unique sk-bf-… virtual key generated per VM on first boot, never baked into the image
  • A shipped dependency license inventory at /usr/share/bifrost/dependency-licences.txt that covers all 907 dependencies compiled into the binary
  • 24/7 SecureAnyCloud support

Prerequisites

  • An Azure subscription with permission to create VMs
  • An API key for at least one model provider (OpenAI, Anthropic, Google, Azure OpenAI, Bedrock and so on). You add this key yourself after launch
  • A Standard_B2s VM or larger. Bifrost is a single Go binary and is designed for sub-100-microsecond overhead at 5000 requests per second, so 2 vCPU and 4 GiB is sufficient for most deployments
  • Inbound TCP 80 open to the networks that your applications and administrators connect from
  • An SSH key pair for administrative access
1

Deploy from the Azure Portal

  • In the Azure Portal, search the Marketplace for Bifrost on Ubuntu 24.04 LTS by SecureAnyCloud and select Create.
  • Choose your subscription, resource group and region.
  • Set the VM size to Standard_B2s or larger.
  • Set the authentication type to SSH public key and supply your key.
  • Under Inbound port rules, allow SSH (22) and HTTP (80).
  • On the Disks tab, choose StandardSSD_LRS for a good balance of cost and performance.
  • Select Review + create, then Create.

First boot takes about a minute. During that minute, the appliance generates this VM's credentials. It also proves that its gateway rejects unauthenticated requests before it opens port 80.

2

Deploy from the Azure CLI

az vm image terms accept --urn sac1647283583153:bifrost-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-resource-group \
  --name bifrost-gateway \
  --image sac1647283583153:bifrost-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open the ports that your applications need:

az vm open-port --resource-group my-resource-group --name bifrost-gateway --port 80 --priority 900

In production, restrict the source range to your own networks, because this gateway relays requests that spend your provider credit.

3

Connect to your VM

ssh azureuser@<vm-ip>
4

Confirm the services are running

Three units make up the appliance. bifrost-firstboot.service runs once and generates this VM's secrets. bifrost.service is the gateway itself, and nginx.service is the access gate in front of it.

systemctl is-active bifrost.service nginx.service bifrost-firstboot.service

Expected output:

active active active

Confirm that the gateway is bound to loopback only. This makes the nginx gate the sole way in, because nothing can reach Bifrost directly from the network.

sudo ss -lntH | awk '{print $1, $4}' | grep -E ':(80|8080)$'

Expected output: Bifrost on 127.0.0.1:8080 and nginx on every address:

LISTEN 127.0.0.1:8080 LISTEN 0.0.0.0:80

nginx answers the gateway's own liveness literal and reveals nothing about your configuration, so it is safe to point a load balancer probe at it:

curl -s http://127.0.0.1/nginx-health

Expected output:

OK
5

Read the per-instance credentials

Every secret that this VM uses was generated on this VM at first boot. Nothing is shared with any other Bifrost image.

sudo cat /root/bifrost-credentials.txt

The file holds four values:

Value What it is for

BIFROST_URL The dashboard address for this VM

BIFROST_USERNAME Dashboard username (always bifrost)

BIFROST_PASSWORD Dashboard password, 32 characters, generated on this VM

BIFROST_VIRTUAL_KEY The sk-bf-... virtual key that your applications authenticate with

Confirm the file is readable only by root:

sudo stat -c '%a %U:%G' /root/bifrost-credentials.txt

Expected output:

600 root:root
6

Open the dashboard

Browse to http://<vm-ip>/. Your browser prompts for a username and password. Enter BIFROST_USERNAME and BIFROST_PASSWORD from the file above.

The dashboard is the full Bifrost control plane: observability, model providers, governance, guardrails, MCP gateway, adaptive routing and the prompt repository.

7

Understand the access model

This appliance puts a default-deny gate in front of Bifrost. The gate is deliberate, and it matters more here than for most products.

An LLM gateway relays requests to providers using your API keys. An unauthenticated gateway with a working provider key configured is an open proxy for your inference spend, and unlike a chat interface, it is machine-callable by design.

Bifrost's own authentication is off by default and is stored as a database toggle rather than a configuration file setting. Bifrost also ships an explicit switch to disable authentication on inference calls, and it permanently exempts several routes regardless of settings. This appliance therefore does not rely on it.

Instead, nginx enforces the gate at server scope, so every route inherits it: the dashboard, the admin API, /metrics, and every OpenAI-, Anthropic- and Google-compatible inference path. There is exactly one unauthenticated location, nginx's own /nginx-health literal, which never touches Bifrost.

Verify the gate with the following requests. An anonymous request to the inference endpoint is refused:

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -H 'Content-Type: application/json' -d '{}' \
  http://127.0.0.1/v1/chat/completions

Expected output:

401

An anonymous request to the metrics endpoint and to the governance API is refused as well:

for R in /metrics /api/config /api/governance/virtual-keys /v1/models; do printf '%s -> %s\n' "$R" "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1$R)" done

Expected output:

/metrics -> 401 /api/config -> 401 /api/governance/virtual-keys -> 401 /v1/models -> 401

A credential that is wrong by a single character is also refused:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer <BIFROST_VIRTUAL_KEY>WRONG" \
  http://127.0.0.1/v1/models

Expected output:

401

The gate accepts six equivalent proofs. Each is compared exactly, and each is unique to this VM:

Proof Who uses it

HTTP Basic with BIFROST_USERNAME / BIFROST_PASSWORD Your browser, and curl -u

A gate cookie issued after the browser handshake Your browser, on every dashboard API call

Authorization: Bearer <BIFROST_VIRTUAL_KEY> The OpenAI SDK, LiteLLM, LangChain

x-api-key: <BIFROST_VIRTUAL_KEY> The Anthropic SDK

x-goog-api-key: <BIFROST_VIRTUAL_KEY> The Google GenAI SDK

x-bf-vk: <BIFROST_VIRTUAL_KEY> Bifrost's native header

The last four are the header idioms that Bifrost itself uses for virtual keys. Your application sends one credential that satisfies both the gate and Bifrost's own governance check, so there is nothing extra to configure in your client.

Why the gate is not a plain password prompt

Two reasons break naive designs. A browser's stored HTTP Basic credential travels in the same Authorization header that a single-page application can overwrite. An OpenAI or Anthropic SDK needs that same header for its API key.

A plain auth_basic would therefore fight the SDK for the header and make legitimate programmatic use impossible. Accepting the virtual key in the header that your SDK already uses solves both problems at once.

The gate cannot be started unconfigured

nginx.service carries two ConditionPathExists markers and an ExecStartPre guard. First boot creates the second marker only after it has proved three things against a throwaway, loopback-only listener that runs this VM's real gate configuration. An unauthenticated request must be answered with 401. A one-character mutation of every credential must be refused. A 401 must never leak the gate cookie. If that proof fails, port 80 is never opened.

ls -l /var/lib/sac/data/bifrost-gate.ready
8

Add your own model provider

The image ships with zero providers configured. This is intentional, because a baked-in provider key would hand every customer the same credential.

In the dashboard, go to Models > Model Providers > Add provider, choose your provider, and paste your own API key. Changes take effect immediately.

Before you add a provider, you can confirm from the shell that nothing is configured:

sudo python3 -c "import json;print('providers:', json.load(open('/var/lib/bifrost/data/config.json'))['providers'])"

Expected output on a freshly launched VM:

providers: {}
9

Point your application at the gateway

Set the base URL to your VM and the API key to BIFROST_VIRTUAL_KEY. That single value authenticates through the gate and identifies the virtual key inside Bifrost.

With curl:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: Bearer <BIFROST_VIRTUAL_KEY>" \
  http://127.0.0.1/v1/models

Expected output, which shows that the gateway accepted the key:

200

With the OpenAI Python SDK, the only change to existing code is base_url and api_key:

from openai import OpenAI client = OpenAI( base_url="http://<vm-ip>/v1", api_key="<BIFROST_VIRTUAL_KEY>", ) response = client.chat.completions.create( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": "Hello!"}], ) print(response.choices[0].message.content)

After you add a provider in Step 8, send a chat completion through the gateway:

curl -X POST http://<vm-ip>/v1/chat/completions \
  -H "Authorization: Bearer <BIFROST_VIRTUAL_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"Hello!"}]}'

The model field is provider/model, so the same endpoint reaches every provider you have configured.

10

Manage governance: budgets, rate limits and more keys

First boot created one virtual key for this VM. It is the credential in /root/bifrost-credentials.txt.

Confirm from the shell that exactly one key exists and that inference authentication is enforced:

curl -s -u "<BIFROST_USERNAME>:<BIFROST_PASSWORD>" http://127.0.0.1/api/governance/virtual-keys | jq '.virtual_keys | length'

Expected output:

1
curl -s -u "<BIFROST_USERNAME>:<BIFROST_PASSWORD>" http://127.0.0.1/api/config | jq '.client_config.enforce_auth_on_inference'

Expected output:

true

In Governance > Virtual Keys, you can issue a key per team, application or customer. You can attach a budget, a rate limit, an expiry and a restriction to specific providers or models. Give each application its own key rather than sharing the appliance key, so that you can revoke and meter them independently.

Bifrost enforces any key that you issue in the dashboard. The nginx gate in front recognizes only the appliance key from first boot. If you want a per-team key to reach the gateway from outside the VM, either put your applications on a network that reaches the VM through the appliance key, or terminate TLS and your own identity provider in front of the appliance and forward the appliance key from there.

11

Where your data lives

sudo ls -la /var/lib/bifrost/data

The directory is 0700 root:root because it holds two sensitive stores: the SQLite configuration store with your provider API keys, and the SQLite log store with the prompts and completions of every request that passes through.

sudo stat -c '%a %U:%G' /var/lib/bifrost/data

Expected output:

700 root:root

To back the appliance up, stop the gateway and copy that directory:

sudo systemctl stop bifrost
sudo tar czf /root/bifrost-backup-$(date +%F).tar.gz -C /var/lib/bifrost data
sudo systemctl start bifrost

If you do not want prompts and completions retained, turn the log store off in Observability > Logs Settings.

12

Review what is in the image and its licenses

Every dependency compiled into the shipped binary was enumerated and license-checked at build time, and the inventory ships with the image.

head -8 /usr/share/bifrost/dependency-licences.txt
awk -F'\t' '$1=="go"||$1=="npm"{print $4}' /usr/share/bifrost/dependency-licences.txt | sort | uniq -c | sort -rn | head -6

That inventory covers 225 Go modules read out of the binary's own build information and 682 npm packages from the pinned dashboard lockfile. Every one is permissive: MIT, Apache-2.0, BSD, ISC or MPL-2.0. There is no AGPL, SSPL, Business Source, Elastic License, Commons Clause or non-commercial component.

Confirm that the shipped binary is the pinned upstream release:

LC_ALL=C grep -a -o 'vcs\.revision=[0-9a-f]\{40\}' /usr/local/bin/bifrost-http | head -1

Expected output:

vcs.revision=a15edc24a8656950a10c6c3c60430b913cfb313b

Day-to-day operation

systemctl status bifrost.service --no-pager --lines=0
sudo systemctl restart bifrost
sudo journalctl -u bifrost -f

Ubuntu's unattended security upgrades are enabled, so nginx and the rest of the OS keep receiving patches. Bifrost itself is a pinned binary, and you upgrade it by deliberately replacing the binary.

Security hardening

  • Restrict port 80. This gateway spends your provider credit. Limit the network security group source range to the networks that your applications and administrators use.
  • Put TLS in front. The appliance serves plain HTTP so that it works on a bare IP out of the box. For anything beyond a private network, terminate TLS at an Azure Application Gateway, a load balancer, or nginx with a certificate for your own hostname.
  • Issue a virtual key per application in Governance, with a budget and a rate limit, rather than sharing the appliance key.
  • Rotate the appliance key by editing it in Governance > Virtual Keys. Then update /etc/nginx/conf.d/bifrost-gate-map.conf with the new value and run sudo systemctl reload nginx.
  • Review the request log retention in Observability > Logs Settings if prompts are sensitive.

Troubleshooting

The dashboard does not prompt for a password, or returns 401 forever. The gate is working. Check that you are using BIFROST_USERNAME and BIFROST_PASSWORD from /root/bifrost-credentials.txt exactly.

nginx will not start. By design, it is held on two markers and a preflight guard. Check what it refused:

sudo /usr/local/sbin/bifrost-preflight.sh --nginx; echo "exit=$?"

Expected output on a healthy appliance:

exit=0

First boot did not complete. Read its log:

sudo journalctl -u bifrost-firstboot.service --no-pager | tail -20

Requests return a provider error. You have not added a provider yet (see Step 8). Until you do, the gateway authenticates and routes the request correctly but has nothing to relay it to.

Support

SecureAnyCloud supports this image 24/7. Raise an issue at secureanycloud.com with the output of systemctl status bifrost and journalctl -u bifrost --no-pager | tail -50.

Deploy on Azure

Launch Bifrost on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure