Skip to main content

Connecting to a Managed SQL Server Instance

This guide covers how to retrieve credentials and connect to a SQL Server cluster managed by the operator, whether from outside the cluster, from within Kubernetes, or via a LoadBalancer.

1. Get the SA password​

The SA password is stored in the Kubernetes Secret you referenced in spec.saPasswordSecret. Retrieve it with:

kubectl get secret <sa-password-secret> -o jsonpath='{.data.MSSQL_SA_PASSWORD}' | base64 -d

Example — if your CR has saPasswordSecret: my-mssql-secret:

kubectl get secret my-mssql-secret -o jsonpath='{.data.MSSQL_SA_PASSWORD}' | base64 -d

Store it in a shell variable for the examples below:

SA_PASSWORD=$(kubectl get secret my-mssql-secret -o jsonpath='{.data.MSSQL_SA_PASSWORD}' | base64 -d)

2. Connecting via port-forward (from your local machine)​

Port-forward works regardless of Service type and is the simplest way to test connectivity from your workstation. It connects to a single pod directly.

kubectl port-forward pod/my-mssql-0 1433:1433

In another terminal, connect with sqlcmd:

# Legacy-style flags (go-sqlcmd v18+ still supports these)
sqlcmd -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" -C -Q "SELECT @@VERSION"

# New go-sqlcmd subcommand style
sqlcmd query "SELECT @@VERSION" -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" --trust-server-certificate

-C / --trust-server-certificate tells sqlcmd to accept the operator's self-signed certificate without CA validation.

Or with mssql-cli:

mssql-cli -S localhost,1433 -U sa -P "$SA_PASSWORD"

Or via a connection string (e.g. Python / .NET / Go):

Server=localhost,1433;User Id=sa;Password=<password>;Encrypt=True;TrustServerCertificate=True;

3. Connecting from within Kubernetes​

Using the AG-aware services (Availability Groups)​

When AG is enabled, the operator creates two ClusterIP services that route traffic based on the current AG role:

ServiceDNS nameRoutes to
<cr-name>-primary<cr-name>-primary.<namespace>.svc.cluster.localCurrent PRIMARY (read-write)
<cr-name>-replicas<cr-name>-replicas.<namespace>.svc.cluster.localSECONDARY replicas (read-only)

These services automatically follow failovers — the operator updates pod labels when the primary changes.

Connect to the primary (read-write):

kubectl port-forward svc/my-mssql-primary 1433:1433
sqlcmd -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" -C -Q "SELECT @@SERVERNAME"

Connect to a replica (read-only):

kubectl port-forward svc/my-mssql-replicas 1433:1433
sqlcmd -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" -C -Q "SELECT @@SERVERNAME"

From an application pod:

env:
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: my-mssql-secret
key: MSSQL_SA_PASSWORD
- name: MSSQL_RW_CONNECTION_STRING
value: "Server=my-mssql-primary.default.svc.cluster.local,1433;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True;"
- name: MSSQL_RO_CONNECTION_STRING
value: "Server=my-mssql-replicas.default.svc.cluster.local,1433;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True;"

Using the headless Service​

The operator creates a headless Service named after the CR. Each pod is reachable at a stable DNS name:

<cr-name>-<ordinal>.<cr-name>.<namespace>.svc.cluster.local

Example — pod 0 of my-mssql in the default namespace:

my-mssql-0.my-mssql.default.svc.cluster.local

Run a one-off sqlcmd pod in the cluster:

kubectl run sqlcmd --rm -it --image mcr.microsoft.com/mssql-tools --restart=Never -- \
/opt/mssql-tools/bin/sqlcmd \
-S "my-mssql-0.my-mssql.default.svc.cluster.local,1433" \
-U sa -P "$SA_PASSWORD" -C \
-Q "SELECT NAME FROM sys.databases"

To hit any ready replica (load-balanced via the headless Service's DNS round-robin), use the service hostname without an ordinal:

kubectl run sqlcmd --rm -it --image mcr.microsoft.com/mssql-tools --restart=Never -- \
/opt/mssql-tools/bin/sqlcmd \
-S "my-mssql.default.svc.cluster.local,1433" \
-U sa -P "$SA_PASSWORD" -C \
-Q "SELECT @@SERVERNAME"

The image mcr.microsoft.com/mssql-tools ships the classic sqlcmd binary at /opt/mssql-tools/bin/sqlcmd, which uses legacy-style flags only.

Note: The headless Service has ClusterIP: None — it returns the pod IPs directly via DNS. Client-side DNS-based load balancing applies. For AG workloads, use the <cr-name>-primary service instead.

From an application pod​

Provide the connection string via a Secret or environment variable in your application deployment:

env:
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: my-mssql-secret
key: MSSQL_SA_PASSWORD
- name: MSSQL_CONNECTION_STRING
value: "Server=my-mssql-0.my-mssql.default.svc.cluster.local,1433;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True;"

4. Connecting via LoadBalancer​

To expose SQL Server outside the cluster, enable the expose option in the CR. When AG is enabled, the exposed service automatically routes to the current PRIMARY replica:

spec:
expose:
enabled: true
type: LoadBalancer
# Restrict access to specific CIDRs (recommended for production):
loadBalancerSourceRanges:
- 10.0.0.0/8
- 203.0.113.0/24

After applying, wait for the external IP:

kubectl get service my-mssql-exposed -w
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# my-mssql-exposed LoadBalancer 10.43.12.34 203.0.113.55 1433:31234/TCP 30s

Store the address:

EXTERNAL_IP=$(kubectl get service my-mssql-exposed -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

Connect:

# Legacy-style
sqlcmd -S "tcp:${EXTERNAL_IP},1433" -U sa -P "$SA_PASSWORD" -C -Q "SELECT @@SERVERNAME"

# New go-sqlcmd style
sqlcmd query "SELECT @@SERVERNAME" -S "tcp:${EXTERNAL_IP},1433" -U sa -P "$SA_PASSWORD" --trust-server-certificate

With Azure Data Studio or SSMS, use:

  • Server: <EXTERNAL_IP>,1433
  • Authentication: SQL Login
  • Username: sa
  • Password: from the Secret
  • Enable Trust server certificate if using the operator-generated self-signed cert, or add the CA to your trust store (see tls.md).

Using NodePort​

For clusters without LoadBalancer support (e.g. bare metal, local dev clusters):

spec:
expose:
enabled: true
type: NodePort

Find the assigned node port:

kubectl get service my-mssql-exposed -o jsonpath='{.spec.ports[0].nodePort}'

Get a node IP:

kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}'

Connect:

sqlcmd -S "tcp:<NODE_IP>,<NODE_PORT>" -U sa -P "$SA_PASSWORD" -C -Q "SELECT @@VERSION"
# or
sqlcmd query "SELECT @@VERSION" -S "tcp:<NODE_IP>,<NODE_PORT>" -U sa -P "$SA_PASSWORD" --trust-server-certificate

5. TLS and certificate trust​

TLS is enabled by default. The operator generates a self-signed certificate stored in a Secret named <cr-name>-tls-certs.

Quick option: trust the server certificate without validation​

All sqlcmd examples above use -C (TrustServerCertificate). This is convenient but skips certificate chain validation.

Proper option: trust the operator CA​

Extract the CA certificate:

kubectl get secret my-mssql-tls-certs -o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/mssql-ca.crt

Use it with sqlcmd:

sqlcmd -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" \
--driver-option "CAFile=/tmp/mssql-ca.crt" \
-Q "SELECT @@VERSION"

Or set the SQLCMDDBNAME equivalent in your connection string:

Server=localhost,1433;User Id=sa;Password=<password>;Encrypt=True;TrustServerCertificate=False;

Import /tmp/mssql-ca.crt into your system or application trust store.

Disabling TLS (for local development only)​

spec:
tls:
enabled: false

Connect without encryption:

sqlcmd -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" -N o -Q "SELECT @@VERSION"
# or
sqlcmd query "SELECT @@VERSION" -S "tcp:localhost,1433" -U sa -P "$SA_PASSWORD" --encrypt optional

Tooling reference​

ToolInstallNotes
sqlcmd (go-sqlcmd v18+)brew install sqlcmd / Microsoft docsCLI, supports both legacy -S/-U/-P flags and new subcommand syntax (sqlcmd query)
mssql-clipip install mssql-cliInteractive REPL with autocomplete
Azure Data StudioDownloadGUI, cross-platform
SSMSDownloadWindows only
Container imagemcr.microsoft.com/mssql-toolsClassic sqlcmd inside Kubernetes (legacy flags only)