Summit 26 from June 1-4 in San Francisco

Lead your organization in the era of agents and enterprise intelligence.

Snowflake for DevelopersGuidesDeploy pgAdmin to SPCS Connecting to Snowflake Postgres via Private Link
Quickstart

Deploy pgAdmin to SPCS Connecting to Snowflake Postgres via Private Link

Brian Pace

Overview

What You Will Build

This quickstart walks through deploying pgAdmin4 as a Snowpark Container Services (SPCS) service and connecting it to a Snowflake Postgres instance over Private Link. All database traffic stays on Snowflake's private network backbone and never leaves the Snowflake environment. This example uses the pgAdmin container as an example, but the same steps can be used to deploy other containers that need Postgres.

pgAdmin is an open-source administration and development platform for PostgreSQL. It provides a web-based interface for managing databases, writing and executing queries, monitoring server activity, and visualizing data. Running pgAdmin inside SPCS rather than on an external machine eliminates the need to expose your Postgres instance to the public internet or configure VPN tunnels. The connection uses Private Link over Snowflake's internal network, reducing latency, simplifying network security, and ensuring that credentials and query traffic never traverse external networks.

Instance name placeholder: All examples use PG_LAB as the Snowflake Postgres instance name. Substitute your own instance name throughout.

Note: Private Link requires Snowflake Business Critical edition or higher.

Architecture

Browser
  |  HTTPS
  v
pgAdmin4 Service  (SPCS)
  |  TCP 5432 via EAI (PRIVATE_HOST_PORT rule)
  v
SPCS Private Link Endpoint  <- provisioned by SYSTEM$PROVISION_PRIVATELINK_ENDPOINT
  |  Snowflake private network
  v
Snowflake Postgres Instance  (Private Link Service)

Key Components

ComponentPurpose
SPCS Compute PoolProvides the compute resources that run the pgAdmin container
Image RepositoryStores the pgAdmin container image inside Snowflake
Snowflake Postgres Private LinkExposes the Postgres instance as a Private Link Service on the Snowflake network
SYSTEM$PROVISION_PRIVATELINK_ENDPOINTCreates the SPCS-side Private Link endpoint automatically without cloud portal work
External Access Integration (EAI)Grants the container permission to open TCP connections to the private Postgres hostname
Snowflake SecretStores pgAdmin admin credentials; injected securely into the container as environment variables

Prerequisites

  • Snowflake account with ACCOUNTADMIN access
  • Snowflake CLI (snow) installed and configured
  • Docker installed locally (for pulling and pushing the pgAdmin image)
  • Account-level Private Link already enabled between your Snowflake account and your cloud network. See AWS PrivateLink and Snowflake or Azure Private Link and Snowflake for account-level setup if not already done.
  • An active Snowflake Postgres instance (PG_LAB in these examples)

SPCS Infrastructure Setup

All SPCS objects (stage, image repository, service) live in a single database and schema to keep them organized.

Step 1: Create Database, Schema, and Image Repository

-- SPCS Infrastructure Setup: Step 1 - Create Database, Schema, and Image Repository
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

CREATE DATABASE IF NOT EXISTS SPGAPP;
CREATE SCHEMA  IF NOT EXISTS SPGAPP.SPCS;

CREATE STAGE IF NOT EXISTS SPGAPP.SPCS.SPGAPP_STAGE;

CREATE IMAGE REPOSITORY IF NOT EXISTS SPGAPP.SPCS.IMAGES;

The stage holds service spec files and persistent data volumes. The image repository is the private Docker registry where you will push the pgAdmin image.

Step 2: Create Compute Pool

The compute pool defines the virtual machine type that runs the container. A small CPU node (CPU_X64_S) is sufficient for pgAdmin.

-- SPCS Infrastructure Setup: Step 2 - Create Compute Pool
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

CREATE COMPUTE POOL IF NOT EXISTS SPGAPP_POOL
  MIN_NODES         = 1
  MAX_NODES         = 2
  INSTANCE_FAMILY   = 'CPU_X64_S'
  AUTO_RESUME       = TRUE
  AUTO_SUSPEND_SECS = 300;

AUTO_SUSPEND_SECS = 300 suspends the pool after 5 minutes of inactivity to avoid idle credit consumption.

Step 3: Push the pgAdmin Image to the Snowflake Registry

SPCS requires images to be stored in its own registry. The steps below pull the official pgAdmin image, re-tag it for your Snowflake registry, and push it.

# 1. Get your registry URL (returns something like abc123.registry.snowflakecomputing.com)
snow spcs image-registry url -c <your-connection>

# 2. Authenticate Docker to the Snowflake registry
snow spcs image-registry login -c <your-connection>

# 3. Pull the source image for the required architecture
#    SPCS runs on linux/amd64 - always specify the platform when pulling on Apple Silicon
docker pull --platform linux/amd64 dpage/pgadmin4:latest

# 4. Tag it for the Snowflake registry (image paths must be lowercase)
docker tag dpage/pgadmin4:latest <registry-url>/spgapp/spcs/images/pgadmin4:latest

# 5. Push to the Snowflake registry
docker push <registry-url>/spgapp/spcs/images/pgadmin4:latest

Private Link for a Snowflake Postgres instance is enabled per instance, independently of the account-level Private Link. The operation is asynchronous and can take up to 10 minutes.

Step 1: Enable Private Link

-- Enable Private Link on Postgres Instance: Step 1 - Enable Private Link
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

ALTER POSTGRES INSTANCE PG_LAB ENABLE PRIVATELINK;

Step 2: Wait for Private Link to Become Available

Poll DESCRIBE POSTGRES INSTANCE until privatelink_service_identifier is populated.

-- Enable Private Link on Postgres Instance: Step 2 - Wait for Private Link to Become Available
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

DESCRIBE POSTGRES INSTANCE PG_LAB;

Step 3: Capture Connection Details

Once privatelink_service_identifier is populated, store the values for use in subsequent steps:

-- Enable Private Link on Postgres Instance: Step 3 - Capture Connection Details
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

SET plinksi     = '<privatelink_service_identifier value from DESCRIBE>';
SET pghost      = '<host value from DESCRIBE>';
SET pghost_port = $pghost || ':5432';

$pghost_port combines the hostname with the PostgreSQL port. It is used when defining the egress network rule later, which requires host:port format.

SYSTEM$PROVISION_PRIVATELINK_ENDPOINT is the key step that makes this approach different from a standard cloud Private Link setup. Instead of manually creating an Azure Private Endpoint or AWS VPC Endpoint in the cloud portal, this Snowflake function handles the cloud provider plumbing automatically within the SPCS network fabric. It creates a private endpoint that routes traffic from any SPCS container directly to the Postgres instance's Private Link Service over Snowflake's internal backbone.

Step 1: Provision the Endpoint

-- Provision the SPCS Private Link Endpoint: Step 1 - Provision the Endpoint
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT($plinksi, $pghost);

Step 2: Authorize the Connection

After provisioning, a connection request appears on the Postgres instance in PENDING state. Retrieve its connection_id and authorize it:

-- Provision the SPCS Private Link Endpoint: Step 2 - Authorize the Connection
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

SHOW PRIVATELINK CONNECTIONS IN POSTGRES INSTANCE PG_LAB;

ALTER POSTGRES INSTANCE PG_LAB AUTHORIZE PRIVATELINK CONNECTIONS = ('<connection_id from SHOW PRIVATELINK CONNECTIONS>');

Step 3: Verify the Connection

-- Provision the SPCS Private Link Endpoint: Step 3 - Verify the Connection
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

SHOW PRIVATELINK CONNECTIONS IN POSTGRES INSTANCE PG_LAB;

The status should now show APPROVED.

Configure SPCS Network Access

SPCS containers are network-isolated by default. To allow the pgAdmin container to open a TCP connection to the Postgres private endpoint, you must create an egress network rule, a secret for credentials, and an External Access Integration.

Step 1: Create the Egress Network Rule

The PRIVATE_HOST_PORT type is specifically designed for Private Link targets. It instructs SPCS to route the connection through the provisioned Private Link endpoint rather than over the public internet.

-- Configure SPCS Network Access: Step 1 - Create the Egress Network Rule
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;
USE SCHEMA SPGAPP.SPCS;

CREATE OR REPLACE NETWORK RULE spg_private_rule
  MODE       = EGRESS
  TYPE       = PRIVATE_HOST_PORT
  VALUE_LIST = ($pghost_port);

Step 2: Create the pgAdmin Credentials Secret

Snowflake Secrets store sensitive values and inject them into containers as environment variables at runtime. pgAdmin uses PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD to create the initial admin account on first startup.

-- Configure SPCS Network Access: Step 2 - Create the pgAdmin Credentials Secret
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;
USE SCHEMA SPGAPP.SPCS;

CREATE OR REPLACE SECRET pgadmin_password
  TYPE     = PASSWORD
  USERNAME = '[email protected]'
  PASSWORD = 'change-me-before-production';

Security note: Change the password to something strong before deploying. The secret value is encrypted at rest and only decrypted at container start time.

Step 3: Create the External Access Integration

The External Access Integration (EAI) binds the network rule and the secret together into a single object that can be attached to a service. A service can only reach network targets and access secrets that are explicitly listed in its EAI.

-- Configure SPCS Network Access: Step 3 - Create the External Access Integration
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION spg_pg_lab_eai
  ALLOWED_NETWORK_RULES          = (spgapp.spcs.spg_private_rule)
  ALLOWED_AUTHENTICATION_SECRETS = (spgapp.spcs.pgadmin_password)
  ENABLED                        = TRUE;

Deploy the pgAdmin Service

Step 1: Create the Service

The service spec defines the container image, environment variables, ports, health check, and volumes.

-- Deploy the pgAdmin Service: Step 1 - Create the Service
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;
USE SCHEMA SPGAPP.SPCS;

CREATE SERVICE IF NOT EXISTS PGADMIN4
  IN COMPUTE POOL SPGAPP_POOL
  MIN_INSTANCES = 1
  MAX_INSTANCES = 1
  FROM SPECIFICATION $$
spec:
  containers:
    - name: pgadmin4
      image: "/spgapp/spcs/images/pgadmin4:latest"
      secrets:
        - snowflakeSecret: spgapp.spcs.pgadmin_password
          secretKeyRef: username
          envVarName: PGADMIN_DEFAULT_EMAIL
        - snowflakeSecret: spgapp.spcs.pgadmin_password
          secretKeyRef: password
          envVarName: PGADMIN_DEFAULT_PASSWORD
      env:
        PGADMIN_LISTEN_ADDRESS:                    "0.0.0.0"
        PGADMIN_LISTEN_PORT:                       "80"
        PGADMIN_DISABLE_POSTFIX:                   "1"
        PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "False"
      readinessProbe:
        port: 80
        path: /misc/ping
      volumeMounts:
        - name: pgadmin-data
          mountPath: /var/lib/pgadmin
  endpoints:
    - name: http
      port: 80
      public: true
  volumes:
    - name: pgadmin-data
      source: local
      uid: 5050
      gid: 5050
$$;

Key spec settings:

SettingPurpose
secretsInjects credentials from the Snowflake Secret at container start
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "False"Required when pgAdmin runs behind a reverse proxy (SPCS terminates TLS)
PGADMIN_DISABLE_POSTFIX: "1"Disables the built-in mail server (not needed in SPCS)
source: localBlock storage for pgAdmin's internal SQLite database; stage-backed volumes cause SQLite locking errors
uid: 5050 / gid: 5050pgAdmin runs as the pgadmin user; the volume must be writable by this UID
readinessProbeSPCS waits for /misc/ping to return HTTP 200 before routing traffic to the container

Step 2: Attach the External Access Integration

The EAI must be attached after the service is created to allow the container to reach the Postgres private endpoint.

-- Deploy the pgAdmin Service: Step 2 - Attach the External Access Integration
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

ALTER SERVICE SPGAPP.SPCS.PGADMIN4
  SET EXTERNAL_ACCESS_INTEGRATIONS = (spg_pg_lab_eai);

Step 3: Grant Access to the Service Endpoint

By default only ACCOUNTADMIN can use a new service. Grant your role access to the public HTTP endpoint:

-- Deploy the pgAdmin Service: Step 3 - Grant Access to the Service Endpoint
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

GRANT SERVICE ROLE SPGAPP.SPCS.PGADMIN4!ALL_ENDPOINTS_USAGE TO ROLE <your_role>;

Step 4: Wait for the Service to Start

Service startup typically takes 1-3 minutes while the compute pool provisions and the container passes its readiness probe.

-- Deploy the pgAdmin Service: Step 4 - Wait for the Service to Start
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

DESCRIBE SERVICE SPGAPP.SPCS.PGADMIN4;

SHOW ENDPOINTS IN SERVICE SPGAPP.SPCS.PGADMIN4;

The ingress_url will be an address like: https://abc123-...-spgapp-spcs-pgadmin4.snowflakecomputing.app

Connect pgAdmin to Snowflake Postgres

Open the ingress_url in your browser. The first login you will see is your Snowflake authentication to access the endpoint, not the pgAdmin authentication.

After authenticating with Snowflake, log in to pgAdmin with the credentials stored in the pgadmin_password secret ([email protected] / change-me-before-production unless you changed them).

Register the Snowflake Postgres Connection

In pgAdmin: Object > Register > Server, then fill in the tabs:

General tab:

FieldValue
NamePG_LAB (or any label you prefer)

Connection tab:

FieldValue
Host name/addressThe host value from DESCRIBE POSTGRES INSTANCE PG_LAB
Port5432
Maintenance databasepostgres
UsernameYour Snowflake Postgres admin user (e.g. snowflake_admin)
PasswordYour Snowflake Postgres password

SSL tab:

FieldValue
SSL modeRequire

Snowflake Postgres requires TLS on all connections. Setting SSL mode to Require (or higher) is mandatory.

Click Save. pgAdmin will open a connection through the Private Link endpoint. All traffic travels over Snowflake's private network backbone with no exposure to the public internet.

Monitoring and Troubleshooting

Check Service Status

snow spcs service describe SPGAPP.SPCS.PGADMIN4 -c <your-connection>
snow spcs service list-instances SPGAPP.SPCS.PGADMIN4 -c <your-connection>

View Container Logs

snow spcs service logs SPGAPP.SPCS.PGADMIN4 \
  --container-name pgadmin4 \
  --instance-id 0 \
  -c <your-connection>

Common Issues

SymptomLikely CauseFix
Service stuck in PENDINGCompute pool still startingWait 2-3 min; check SHOW COMPUTE POOLS
Service immediately FAILEDBad spec or image pathCheck logs; verify image path is lowercase and image was pushed
Endpoint returns 502/503Container not readyWatch logs; confirm readiness probe at /misc/ping is passing
Can't connect to Postgres from pgAdminMissing or wrong EAIVerify ALTER SERVICE ... SET EXTERNAL_ACCESS_INTEGRATIONS was run; check network rule VALUE_LIST includes the correct host:5432
Postgres connection timeoutPrivate Link endpoint not approvedRe-run SHOW PRIVATELINK CONNECTIONS IN POSTGRES INSTANCE PG_LAB and confirm status is APPROVED
pgAdmin login page doesn't loadCookie protection mismatchConfirm PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "False" is set in the spec
SYSTEM$PROVISION_PRIVATELINK_ENDPOINT failsAccount-level Private Link not enabledComplete account-level Private Link setup first (see Prerequisites)

Suspend and Resume to Save Credits

Suspending the service stops the compute pool from consuming credits when pgAdmin is not needed. Connection definitions survive a suspend/resume cycle because they are stored on the local volume (which persists while the pool is suspended, but not if the pool is dropped).

-- Suspend and resume the service
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

ALTER SERVICE      SPGAPP.SPCS.PGADMIN4 SUSPEND;
ALTER SERVICE      SPGAPP.SPCS.PGADMIN4 RESUME;

ALTER COMPUTE POOL SPGAPP_POOL SUSPEND;
ALTER COMPUTE POOL SPGAPP_POOL RESUME;

Cleanup

Step 1: Revoke Private Link and Drop Service

-- Cleanup: Step 1 - Revoke Private Link and Drop Service
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

ALTER POSTGRES INSTANCE PG_LAB REVOKE PRIVATELINK CONNECTIONS = ($connid);

DROP SERVICE           IF EXISTS SPGAPP.SPCS.PGADMIN4;
DROP INTEGRATION       IF EXISTS spg_pg_lab_eai;
DROP NETWORK RULE      IF EXISTS spgapp.spcs.spg_private_rule;
DROP SECRET            IF EXISTS spgapp.spcs.pgadmin_password;

Step 2: Drop Compute and Storage (Optional)

-- Cleanup: Step 2 - Drop Compute and Storage (Optional)
-- Execute in: Snowsight (Snowflake)
USE ROLE ACCOUNTADMIN;

ALTER COMPUTE POOL    SPGAPP_POOL SUSPEND;
DROP COMPUTE POOL     IF EXISTS SPGAPP_POOL;
DROP IMAGE REPOSITORY IF EXISTS SPGAPP.SPCS.IMAGES;
DROP STAGE            IF EXISTS SPGAPP.SPCS.SPGAPP_STAGE;
DROP SCHEMA           IF EXISTS SPGAPP.SPCS;
DROP DATABASE         IF EXISTS SPGAPP;

Conclusion and Resources

What You Learned

Congratulations! You have successfully:

  • Created the SPCS infrastructure (compute pool, image repository, stage)
  • Pushed the pgAdmin container image to the Snowflake registry
  • Enabled Private Link on a Snowflake Postgres instance
  • Provisioned and authorized the SPCS Private Link endpoint
  • Configured network access with egress rules and an External Access Integration
  • Deployed pgAdmin4 as an SPCS service
  • Connected pgAdmin to Snowflake Postgres over Private Link with no public internet exposure

Key Capabilities Demonstrated

SPCSSnowflake Postgres
Container service deploymentPrivate Link Service
External Access Integration (EAI)SYSTEM$PROVISION_PRIVATELINK_ENDPOINT
Private network egress rulesConnection authorization
Secrets for credential injectionTLS-required connections

Related Resources

Updated 2026-05-05

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances