Deployment guide

Babelfish

Overview

Babelfish for PostgreSQL gives one PostgreSQL cluster a second front door. Alongside the usual PostgreSQL frontend protocol, it runs a TDS listener. TDS is the wire protocol that Microsoft SQL Server speaks, so an unmodified SQL Server client can connect, issue T-SQL and get SQL Server semantics: @@VERSION, SELECT TOP n, GETDATE(), sys.databases, CREATE LOGIN, and the rest.

The data lives in PostgreSQL the whole time and is readable through ordinary SQL with psql or any PostgreSQL tool. An application written against SQL Server can be pointed at PostgreSQL without rewriting its queries or swapping its driver.

This guide covers the deployment and configuration of Babelfish for PostgreSQL on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. The image ships Babelfish 5.4.0 on the project's own patched PostgreSQL 17.7 engine, compiled from source against exact upstream commits.

What you get:

  • Babelfish 5.4.0 with all four extensions (babelfishpg_common, babelfishpg_money, babelfishpg_tds, babelfishpg_tsql) built and installed into an initialised cluster
  • The patched PostgreSQL 17.7 engine that Babelfish requires. Babelfish does not run on community PostgreSQL, and no community PostgreSQL server is installed on this image
  • A cluster that is already initialised: sys.initialize_babelfish has run, master, tempdb and msdb exist, and the appliance answers a T-SQL query the moment it finishes booting
  • multi-db migration mode and the standard sql_latin1_general_cp1_ci_as server collation, both fixed at initialisation and unchangeable afterwards
  • TLS built in. Upstream's documented build omits OpenSSL, which leaves the TDS listener unable to offer encryption at all. This build enables it, and every VM generates its own key pair on first boot
  • No credential in the image. Every account ships with no password, not a default one
  • The FreeTDS tsql client, so you can verify the appliance from the VM itself without installing anything
  • A full dependency licence inventory at /usr/share/babelfish/dependency-licences.txt and third-party notices at /usr/share/babelfish/THIRD-PARTY-NOTICES
  • 24/7 SecureAnyCloud support

There is no web interface

Babelfish is a database engine. There is no dashboard, no admin console and no REST API on this image, and no web server is installed. You administer it over SSH and use it with a SQL client.

For a graphical tool, point SQL Server Management Studio, Azure Data Studio, DBeaver or pgAdmin at it from your own workstation.

Ports and network access

Restrict the network security group to your own network before you put data on this VM. An internet-reachable database is the classic breach, and port 1433 is the first thing scanners look for. SecureAnyCloud gives every VM a strong, unique password and ships no default credential at all, but a password does not replace a firewall.

Port Protocol Purpose Reachable on a fresh VM

22 TCP SSH: how you administer the appliance Yes

1433 TCP TDS: the SQL Server wire protocol. This is the product Yes

5432 TCP PostgreSQL frontend protocol Yes, see below

Both database ports are bound, and that is not a configuration choice. Babelfish's TDS listener does not create its own sockets from its own setting. It iterates PostgreSQL's listen_addresses and creates one TDS socket per entry.

Setting listen_addresses = 'localhost' to hide 5432 also binds the TDS listener to loopback and makes the product unreachable. The two ports stand or fall together.

Instead, this image removes everything that 5432 could reach and 1433 does not already reach. pg_hba.conf admits remote clients only to the Babelfish database and rejects every other remote connection outright. As a result, postgres, template0 and template1 are unreachable from the network by any protocol. A remote psql has exactly the reach that a remote SQL Server client already has, and no more. You can see that rule set, and check it yourself, in Verifying the appliance below.

Restricting the ports

In the Azure Portal, open the VM's Networking blade and edit the inbound rules. Set the Source for ports 22, 1433 and 5432 to IP Addresses, using your own office or VPN range, not Any. From the Azure CLI, run this on your own workstation:

# Replace 203.0.113.0/24 with your own network, and the names with yours.
az network nsg rule create \
  --resource-group my-resource-group \
  --nsg-name my-vm-nsg \
  --name allow-tds-from-office \
  --priority 300 \
  --source-address-prefixes 203.0.113.0/24 \
  --destination-port-ranges 1433 5432 \
  --access Allow --protocol Tcp

To avoid exposing the database at all, leave 1433 and 5432 closed in the NSG and reach them over an SSH tunnel from your workstation:

ssh -L 1433:127.0.0.1:1433 -L 5432:127.0.0.1:5432 azureuser@<vm-ip>

Your SQL client then connects to 127.0.0.1,1433 on your own machine.

Prerequisites

Before you launch, make sure you have:

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair for VM access
  • A VM size of Standard_B2ms (2 vCPU, 8 GiB) or larger for real work. Standard_B2s (4 GiB) is fine for evaluation

The appliance is not heavy at rest. The engine idles at a few hundred MB, and no swap is created at any point. However, every T-SQL backend loads the ANTLR-generated T-SQL parser, so working memory grows faster per connection than in plain PostgreSQL.

8 GiB is comfortable, and 4 GiB will run a demo. Upstream's own 4 GiB floor applies to compiling Babelfish, which you never have to do because this image is already built.

Launching the VM

From the Azure Portal

  • Search the Azure Marketplace for Babelfish for PostgreSQL by SecureAnyCloud and select Create.
  • Choose your subscription, resource group and region.
  • Pick Standard_B2ms or larger.
  • Select SSH public key authentication with the username azureuser.
  • Under Inbound port rules, allow SSH (22) only for now. You add 1433 with a restricted source afterwards.
  • Select Create and wait for the deployment to finish.

From the Azure CLI

az vm create \
  --resource-group my-resource-group \
  --name babelfish-01 \
  --image sac1647283583153:babelfish-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

If prompted, accept the image terms once per subscription. This is an Azure control-plane command that you run from your own machine:

az vm image terms accept --urn sac1647283583153:babelfish-ubuntu-24-04:default:latest

First boot and your credentials

The image contains no password at all, not a default one and not a well-known one. Every login role ships with PASSWORD NULL, which cannot authenticate over the network by any method under the shipped client-authentication rules. Until first boot finishes, nobody can use the appliance.

On first boot, the appliance runs these steps in order:

  • Generates 128 bits of entropy per account from /dev/urandom.
  • Generates this VM's own TLS key pair.
  • Starts the engine bound to 127.0.0.1 only.
  • Sets the passwords.
  • Proves the product works. A real T-SQL session over TDS creates a table, inserts a row and reads it back, and PostgreSQL then reads the same row. The proof is then torn down again.
  • Writes the credentials to /root/babelfish-credentials.txt, mode 0600, owned by root.
  • Only then opens the routable listener and audits every socket on the machine.

If any step fails, the port never opens. That ordering is deliberate.

First boot takes well under a minute. SSH in and read your credentials:

sudo cat /root/babelfish-credentials.txt

The file has this shape, with values unique to your VM:

BABELFISH_HOST=10.0.0.13 BABELFISH_TDS_PORT=1433 BABELFISH_LOGIN=babelfish_user BABELFISH_PASSWORD=<set-at-first-boot> BABELFISH_APP_LOGIN=sac_app BABELFISH_APP_PASSWORD=<set-at-first-boot> POSTGRES_USER=postgres POSTGRES_PASSWORD=<set-at-first-boot> POSTGRES_PORT=5432 POSTGRES_DATABASE=babelfish_db

Three accounts are provisioned:

Account What it is Use it for

babelfish_user The Babelfish sysadmin login. Owns the Babelfish database and is a PostgreSQL superuser Administration: creating databases, creating logins, schema changes

sac_app An unprivileged T-SQL login, created through T-SQL itself on first boot Your application's day-to-day connection

postgres The PostgreSQL superuser Maintenance through psql

Copy these into your own secret store. You can then delete the file if you prefer. After first boot, only the start-up guard reads it, and it only checks that the file exists and is 0600 root:root.

Confirm that first boot completed and that the service is up:

test -f /var/lib/sac/data/babelfish-firstboot.done && echo "first boot complete" systemctl is-active babelfish.service

Verifying the appliance

The kernel lists every listening socket on the machine, and the appliance ships an audit that refuses anything unexpected:

sudo ss -lntuH sudo /usr/local/sbin/babelfish-socket-audit.sh --expect-running

The audit classifies every socket by whether its bind address is reachable from off the machine, rather than by a list of port numbers. It exits non-zero if anything other than SSH, TDS, PostgreSQL and the DHCP client is routable.

Read the client-authentication rules that confine port 5432:

sudo grep -vE '^\s*#|^\s*$' /var/lib/babelfish/5.4.0/data/pg_hba.conf

The host babelfish_db all 0.0.0.0/0 md5 line lets remote SQL Server clients in. The host all all 0.0.0.0/0 reject line beneath it stops any remote connection from reaching anything else.

Why md5 and not scram-sha-256. Babelfish's TDS login handler accepts only trust, password, md5 and gssapi. It rejects a scram-sha-256 rule outright. With the PostgreSQL 14+ default of scram-sha-256 here, psql would work and every SQL Server client would be refused.

So the rules say md5, while password_encryption is scram-sha-256. What is actually stored is a SCRAM verifier, never an MD5 hash. Confirm it with:

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -tAc \
  "SELECT rolname, left(rolpassword, 13) FROM pg_authid WHERE rolcanlogin AND rolpassword IS NOT NULL;"

Your first T-SQL session

The appliance ships the FreeTDS tsql client, so you can verify it from the VM itself. GO terminates each batch, exactly as in sqlcmd. Run:

tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' SELECT @@VERSION GO SQL

The output reports Babelfish for PostgreSQL with SQL Server Compatibility, the Babelfish version and the PostgreSQL engine underneath.

Next, create a table, insert rows and read them back with T-SQL syntax that plain PostgreSQL would reject:

tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' CREATE TABLE inventory (id INT, part VARCHAR(40), qty INT, updated DATETIME) GO INSERT INTO inventory VALUES (1, 'gasket', 42, GETDATE()), (2, 'flange', 17, GETDATE()) GO SELECT TOP 2 id, part, qty FROM inventory ORDER BY id GO SELECT name, database_id FROM sys.databases GO SQL

SELECT TOP, GETDATE() and sys.databases are all SQL Server constructs. They work here because the T-SQL parser and the SQL Server catalogue views handle them, not because PostgreSQL understands them.

Connecting from your own machine

From a workstation, connect exactly as you would to SQL Server, using the VM's address and port 1433. With Microsoft's sqlcmd, run:

sqlcmd -S <vm-ip>,1433 -U babelfish_user -P '<password>' -d master -N -C -Q "SELECT @@VERSION"

-N -C asks for an encrypted connection and accepts the VM's self-signed certificate. mssql-tools18 requires encryption by default, which is why this build enables TLS. If your tool offers "Trust server certificate", select it, or replace the certificate with one of your own, as described below.

SQL Server Management Studio, Azure Data Studio, DBeaver and any .NET SqlClient connection string work the same way. Use server <vm-ip>,1433, SQL Server authentication, and the login and password from your credentials file.

Babelfish speaks TDS 7.0 to 7.4. It does not implement TDS 8.0, so SQL Server 2022 strict encryption (sqlcmd -N s) cannot connect. Use the standard encrypted mode shown above.

The same data, through PostgreSQL

This is what makes Babelfish more than a compatibility shim. The rows you just wrote with T-SQL are ordinary PostgreSQL rows. In multi-db mode, a T-SQL database's dbo schema is <database>_dbo, so the tables of the master database live in master_dbo.

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -d babelfish_db \
  -c "SELECT id, part, qty FROM master_dbo.inventory ORDER BY id;"

These are the same rows in the same table, reached through a different wire protocol. Every PostgreSQL tool you already own works against this data, including pg_dump, monitoring, replication and extensions.

List the extensions installed in the cluster:

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -d babelfish_db \
  -c "SELECT extname, extversion FROM pg_extension ORDER BY extname;"

Remove the demonstration table when you are done:

tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' DROP TABLE inventory GO SQL

Creating databases and logins

T-SQL CREATE DATABASE creates a Babelfish database, which appears as a set of schemas inside babelfish_db:

tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' CREATE DATABASE salesdemo GO USE salesdemo GO CREATE TABLE orders (id INT, customer VARCHAR(60), total MONEY) GO INSERT INTO orders VALUES (1, 'Contoso', 199.95) GO SELECT id, customer, total FROM orders GO DROP TABLE orders GO USE master GO DROP DATABASE salesdemo GO SQL

The block removes the demonstration database again, so you can run it as many times as you like. Drop the DROP DATABASE line if you want to keep the database.

Create logins with T-SQL too. The unprivileged sac_app login already exists for your application. Create more in the same way:

tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' CREATE LOGIN reporting WITH PASSWORD = 'ChangeThisToSomethingStrong!2026' GO SELECT name FROM sys.server_principals WHERE name = 'reporting' GO SQL

Verify from the PostgreSQL side that the login is not a superuser, then remove the login:

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -tAc \
  "SELECT rolname, rolsuper FROM pg_roles WHERE rolname = 'reporting';" tsql -H 127.0.0.1 -p 1433 -U babelfish_user -P '<BABELFISH_PASSWORD>' -D master <<'SQL' DROP LOGIN reporting GO SQL

What is fixed at initialisation

Three settings are decided when Babelfish is initialised and cannot be changed afterwards without building a new cluster. This image sets them as follows:

Setting Value on this image Meaning

babelfishpg_tsql.migration_mode multi-db Each T-SQL database gets its own schema namespace, <database>_dbo. This is upstream's recommendation and what a migrating SQL Server application expects

babelfishpg_tsql.database_name babelfish_db The PostgreSQL database that holds everything Babelfish

babelfishpg_tsql.server_collation_name sql_latin1_general_cp1_ci_as The SQL Server default: case-insensitive, accent-sensitive

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -d babelfish_db -tAc \
  "SELECT name, setting FROM pg_settings WHERE name LIKE 'babelfishpg_tsql.%' ORDER BY name;"

If you need single-db mode or a different collation, decide before you load data. The only way to change them is to create a new cluster.

Encryption in transit

The appliance generates its own TLS key pair on your VM at first boot. No two VMs from this image share a private key, and no key is baked in. The certificate is self-signed, which is why clients need "trust server certificate" (-C in sqlcmd).

sudo -u postgres /opt/babelfish/5.4.0/bin/psql -h /var/run/postgresql -tAc "SHOW ssl;"
sudo openssl x509 -noout -subject -dates \
  -in /var/lib/babelfish/5.4.0/data/sac-babelfish.crt

To use a certificate from your own authority, replace sac-babelfish.crt and sac-babelfish.key in that directory. Keep the key 0600 and owned by postgres. Then reload the engine with sudo systemctl reload babelfish.service.

Operating the appliance

The engine runs as a single systemd service:

systemctl status babelfish.service --no-pager

PostgreSQL collects the logs itself, one file per day:

sudo ls -1 /var/log/babelfish/
sudo tail -20 /var/log/babelfish/*.log

A convenience wrapper for psql against the local cluster is on PATH:

babelfish-psql -d babelfish_db -c "SELECT current_database(), version();"

Back up exactly as you would any PostgreSQL database. A pg_dump of babelfish_db captures every T-SQL database, because they are all schemas inside it.

The start-up guard

babelfish.service does not start unless the appliance is in its shipped, secure state. Before every start, as root, it checks that:

  • the data directory belongs to the service account;
  • listen_addresses has not been reduced to something that would make the TDS listener unreachable;
  • babelfishpg_tds is still preloaded and the TDS port is still 1433;
  • pg_hba.conf contains no trust rule and still carries its remote reject catch-all;
  • first boot has completed and left a 0600 root:root credentials file.

You can run the check by hand:

sudo /usr/local/sbin/babelfish-preflight.sh

If the service refuses to start, run this check. It reports exactly which invariant is broken.

What is inside, and under what licence

Babelfish is open source. This image is built from source against exact upstream commits, because upstream publishes no packages, no APT repository and no container image. There is nothing else to install.

cat /usr/share/babelfish/provenance.txt

Component Version Licence

Babelfish extensions 5.4.0 (BABEL_5_4_0) Apache License 2.0

PostgreSQL, patched for Babelfish 17.7 (BABEL_5_4_0__PG_17_7) The PostgreSQL Licence

ANTLR 4 C++ runtime 4.13.2 BSD 3-Clause

T-SQL ANTLR grammar vendored MIT

The full licence texts ship on the image, together with an inventory of every operating system package:

cat /usr/share/babelfish/THIRD-PARTY-NOTICES head -12 /usr/share/babelfish/dependency-licences.txt

An executable gate produces that inventory at build time and again on a freshly launched VM. It reads structured licence metadata for every installed package. It fails on any network-copyleft or source-available licence, reports GPL and LGPL components for review rather than blocking on them, and treats unresolved metadata as a failure. You can re-run it yourself:

sudo python3 /usr/share/babelfish/babelfish-licence-gate.py --selftest

Two things are deliberately not on this image. One is Microsoft's mssql-tools/sqlcmd, which ships under a proprietary licence. The other is Babelfish's optional linked-servers feature, which links FreeTDS under the LGPL.

The FreeTDS tsql client is present as a separate program for local verification and is not linked into any Babelfish binary.

Support

SecureAnyCloud supports this image 24/7. Raise an issue at secureanycloud.com with your subscription id and the VM name. Include the output of cat /usr/share/babelfish/provenance.txt, which identifies exactly which build you are running.

Deploy on Azure

Launch Babelfish for PostgreSQL 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