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-certificatetellssqlcmdto 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:
| Service | DNS name | Routes to |
|---|---|---|
<cr-name>-primary | <cr-name>-primary.<namespace>.svc.cluster.local | Current PRIMARY (read-write) |
<cr-name>-replicas | <cr-name>-replicas.<namespace>.svc.cluster.local | SECONDARY 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-toolsships the classicsqlcmdbinary 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>-primaryservice 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
| Tool | Install | Notes |
|---|---|---|
sqlcmd (go-sqlcmd v18+) | brew install sqlcmd / Microsoft docs | CLI, supports both legacy -S/-U/-P flags and new subcommand syntax (sqlcmd query) |
mssql-cli | pip install mssql-cli | Interactive REPL with autocomplete |
| Azure Data Studio | Download | GUI, cross-platform |
| SSMS | Download | Windows only |
| Container image | mcr.microsoft.com/mssql-tools | Classic sqlcmd inside Kubernetes (legacy flags only) |