Introduction
In a previous article, Snowflake App Runtime - Deploy Full-Stack Web Apps Right Next to Your Data from a Single Prompt, I introduced a mechanism that lets you deploy a full-stack web app (Next.js and friends) right next to your data — inside Snowflake itself. In that article, I built a sales dashboard that reads and displays analytical tables in Snowflake.
Snowflake is an extremely strong platform for aggregating and analyzing large volumes of data (OLAP). Business apps, on the other hand, often have moments where row-level transactions happen at high frequency — inserting a single request, or updating and deleting statuses one at a time. For these OLTP (online transaction processing) workloads, a database purpose-built for that job is the right fit.
Snowflake offers Snowflake Postgres for exactly this space. Because you can have a PostgreSQL-compatible database inside Snowflake, using it as the backend DB for an app built on App Runtime lets you keep a business app — one where inserts, updates, and deletes happen frequently — entirely within Snowflake's security perimeter.
In recent years, PostgreSQL has been getting renewed attention as the backend DB for AI agents and applications. Behind this is the fact that it is a proven, standard database in the enterprise, while its rich extension ecosystem lets you stretch it to many use cases. Snowflake Postgres makes it possible to ride this trend on top of the Snowflake platform.
In this article, I'll walk through how to connect an App Runtime app to Snowflake Postgres — in the order of benefits, use cases, and concrete steps. I've also included the record of the verification I ran by hand to confirm "does this really connect?"
Note: If you just want the steps, jump straight to Quick Steps later in the article. The first half is the verification part that explains "why this configuration is the way it is."
Note: This article reflects my personal views and does not represent Snowflake's official position.
Note: (July 2026) Snowflake App Runtime, as introduced in this article, is a feature in Public Preview. It may change significantly in future updates, so please keep that in mind. Snowflake Postgres reached general availability (GA) in February 2026.
A Quick Recap of Snowflake App Runtime
Snowflake App Runtime is a new platform that lets you deploy web apps like Next.js directly onto Snowflake. No infrastructure to build, no Docker expertise required, and authentication (SSO) is applied automatically. The app runs as a dedicated object called an APPLICATION SERVICE on SPCS.
For a fuller overview and how to build one with CoCo (Cortex Code was renamed CoCo at Snowflake Summit 2026), please see my previous article.
In this article, I'll focus on making the data store for that App Runtime app be Snowflake Postgres.
What is Snowflake Postgres?
Snowflake Postgres is a fully managed PostgreSQL service where you can create and manage PostgreSQL instances directly from Snowflake. Here's a quick summary of its characteristics.
| Aspect | Details |
|---|---|
| What it is | A PostgreSQL server running on a dedicated virtual machine managed by Snowflake |
| Compatibility | Existing PostgreSQL tools, drivers, and ORMs work as-is (psql, Prisma, pg, etc.) |
| Versions | Choose from PostgreSQL major versions (16 / 17 / 18) |
| Best at | High-throughput, high-concurrency transaction processing (OLTP) with writes and updates |
| How to connect | Connect directly from any PostgreSQL client via host name, port 5432, and SSL |
| Status | Generally available (GA) |
While Snowflake's regular tables are strong at aggregating and analyzing large volumes of data (OLAP), Snowflake Postgres is good at OLTP such as "inserting, updating, and deleting one row at a time." The official documentation positions Postgres as the choice when you need a high-throughput, high-concurrency operational database. Combined with an App Runtime app, this division of labor fits together cleanly.
Official docs: About Snowflake Postgres
Why App Runtime × Snowflake Postgres?
You could point your app's backend at an external DB (say, an OLTP database in another cloud), but choosing Snowflake Postgres brings the following benefits.
| Benefit | Details |
|---|---|
| Everything stays inside the perimeter | Both the app and the DB live inside Snowflake. You can build a business app with writes and updates without letting data leave your organization |
| Less data movement | Handle OLTP and analytics on the same platform. pg_lake and data mirroring make the handoff to the analytics side smooth |
| Standard Postgres assets work as-is | Use the pg driver and ORMs (Prisma, etc.) directly. No special SDK required |
| Simpler operations | Provisioning, patching, and backups of the DB are left to Snowflake |
If you shape it as "transactions in Snowflake Postgres, analysis of the accumulated data in Snowflake tables," you can do both operational work and analytics within a single platform.
One thing I'm especially watching is data mirroring (Public Preview). It lets you sync writes to Snowflake Postgres into your Snowflake analytics environment in near real time — without building an ETL pipeline. Internally it pushes changes to Apache Iceberg, so they land with low latency while preserving transactional consistency. In other words, you can achieve "OLTP in Snowflake Postgres, analytics in Snowflake" without sacrificing freshness and without ETL. And Snowflake Postgres itself can be stood up quickly, in just a few clicks. This "OLTP through to analytics, end to end and quick to set up" is a big reason to pick Snowflake Postgres as the backend for a business app.
Official blog: How we pushed CDC into Postgres, and turned replication into clockwork
Use Cases
- Request / entry forms: Business apps that write user input to a DB, such as expense requests or inquiry logging
- Approval workflows: Apps that advance a record's state, like request → approval → done
- Master data tools: Apps that add and edit product or customer master data from the screen
- Stateful internal tools: Apps with frequent updates, such as task management or inventory management
All of these are apps where "inserts, updates, and deletes happen frequently" — cases where an OLTP database fits better than an analytical table.
The Sample App We'll Build
In this article, I'll take the approval workflow from the list above and build an "expense approval app." The key points are:
- Beyond just listing (SELECT), it also performs new requests from a form (INSERT) and status updates via approve/reject (UPDATE)
- KPI cards (total requests, pending, approved, and so on) let you grasp the situation at a glance
- The data store is Snowflake Postgres, and writes are reflected instantly
Here's what the finished app looks like. The frontend is built with Next.js, and pending / approved / rejected states are color-coded with status badges.
From here, I'll connect this app to Snowflake Postgres.
Integration Architecture
Here's the overall picture as a diagram. The App Runtime app (Application Service) connects to Snowflake Postgres by way of an External Access Integration (EAI), which I'll cover below.
The key point is that the connection from the app to Snowflake Postgres is treated as "egress to an external network." This is the crux of the whole thing, so in the next chapter I'll verify it hands-on.
Verification: Can App Runtime Really Reach Snowflake Postgres?
The official documentation states that "external connections from a running App Runtime app require an EAI." But since Snowflake Postgres lives inside the same Snowflake, it's natural to wonder, "do I really need an EAI? Won't it just connect without doing anything?" So I deployed a minimal app to find out.
Verification 1: Without an EAI, even name resolution fails
First, I deployed the app without an EAI and tried to connect to Snowflake Postgres. Looking at the app's logs, here's what I saw.
SELFTEST#1 {"connected":false,"error":"getaddrinfo ENOTFOUND <instance-host>.us-west-2.aws.postgres.snowflake.app"}
getaddrinfo ENOTFOUND — in other words, even the host name resolution fails. By default, the running App Runtime container has no egress to the outside at all, so it can't even resolve the Postgres host name. This made it clear that "an EAI is required."
Verification 2: With an EAI, the behavior changes
Next, I created an EAI that allows the Snowflake Postgres host:port and attached it to the running app (I'll show the concrete SQL in the Quick Steps later). The same app's log then changed like this.
SELFTEST#12 {"connected":false,"error":"timeout expired"}
The error changed from ENOTFOUND to timeout expired. Name resolution now succeeds and the connection is being attempted — but this time it times out. That's because Snowflake Postgres is not accepting the connection from the app.
Snowflake Postgres rejects inbound connections by default. To allow one, you need to attach a network policy that includes a network rule in POSTGRES_INGRESS mode to the instance. So the integration requires both of the following permissions:
- The egress permission on the app side (EAI)
- The ingress permission on the Postgres side (network policy)
Verification 3: Which IPs to allow — Snowflake's egress IP ranges
What's left is "which IPs to allow on the Postgres ingress." The thing to keep in mind here is that the IP address Snowflake uses when it connects out is not fixed — it's dynamic. So rather than allowing a single specific IP, you need to know the range of egress IPs Snowflake uses.
The function that tells you this is SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES. It returns the egress IPs Snowflake uses for UDFs, stored procedures, and SPCS external access (App Runtime is included here) as stable CIDR ranges.
SELECT
value:"ipv4_prefix"::VARCHAR AS ip_cidr_range,
value:"effective"::TIMESTAMP AS effective_date,
value:"expires"::TIMESTAMP AS expiration_date
FROM TABLE(FLATTEN(INPUT => PARSE_JSON(SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES())));
When you run it, you get CIDR ranges and expiration dates per region.
IP_CIDR_RANGE | EFFECTIVE_DATE | EXPIRATION_DATE
<CIDR range in my region> | 2025-08-01 00:00:00.000 | 2026-10-22 ...
<CIDR range in my region> | 2025-08-01 00:00:00.000 | 2026-10-22 ...
When I allowed these ranges on the Postgres ingress, the app connected to Snowflake Postgres without issue, and the writes succeeded too. Even though the source IP is dynamic, allowing the range keeps the connection stable.
Note: These ranges have an expiration. As the expiry approaches, new ranges are added, so for production I'd recommend setting up a mechanism to re-fetch them periodically and update the network policy. For details, see the official docs: Protect request inbounds with Snowflake egress IPs.
With that, the correct configuration comes into view. From the next chapter, I'll summarize it as concrete steps.
Quick Steps
Through the verification, the pieces needed for the integration are settled (the EAI on the app side, the ingress permission on the Postgres side). There are two broad ways to proceed. Let me start with the fastest route.
The Fastest Route: Let CoCo Handle It All
The quickest approach is to tell CoCo Desktop (or the CLI) not only to create the app, but also to prepare Snowflake Postgres, create and attach the EAI, and set up the ingress permission — all in one go — and have the snowflake-apps skill run it end to end.
That said, this integration has a knack when it comes to the order of operations. There are two reasons:
- Attaching an EAI to a running app (
ALTER APPLICATION SERVICE) assumes the app is already deployed and exists - The egress IP range you allow on the Postgres ingress is fetched from a Snowflake function
So it goes smoothly if you have CoCo follow this order.
If you tell CoCo this order and configuration up front in the prompt, it will proceed without getting lost. For this expense approval app, the prompt would look something like this.
/snowflake-apps
Build an "expense approval app" in Next.js with Snowflake Postgres as the backend.
[Features]
- List requests, submit a new request from a form (INSERT), and update status via approve/reject (UPDATE)
- KPI cards for total requests, pending, approved, and so on
- Color-code the status (Pending / Approved / Rejected) with badges
[Deploy & connection plan]
- If needed, create the Snowflake Postgres instance from scratch (use its host if one already exists)
- Deploy the app to the APP_DB.APP schema
- Read credentials from a Snowflake Secret (APP_DB.APP.PG_CRED) mounted via app.yml
- Pass the host, port (5432), and DB name as environment variables in app.yml
- Enable SSL for the connection
- After deploy, create an EAI that allows egress to Snowflake Postgres,
and attach it to the app with ALTER APPLICATION SERVICE
- Finally, take the IP ranges from SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES and
set them as a POSTGRES_INGRESS network policy on the Postgres instance
The key is to state up front that it should "mount and read the Secret," "attach the EAI after deploy," and "allow ingress with the egress IP range." Skip these, and the credential handoff or network permissions tend to fall through the cracks — leaving you stuck wondering why it won't connect after deploy.
Just a few rounds of back-and-forth with CoCo based on this prompt, and the whole configuration for this article (deploying the app, the EAI, and the ingress permission) comes together. For everyday use, this fastest route alone is plenty.
So, what is CoCo actually doing behind the scenes? In the next chapter, I'll build the same thing by hand, one piece at a time, to see what's inside. If you're not interested in the internals and just want it to work, you can skip the rest.
Doing It Step by Step
If you do it yourself one step at a time, the flow looks like this. It's also a useful reference when you want to fold this into an existing workflow.
The SQL below assumes a database/schema to hold the network rules and Secret (APP_DB.APP in the examples) and a role that can create EAIs (ACCOUNTADMIN in the examples).
Step 1: Create the Snowflake Postgres Instance
First, create the Snowflake Postgres instance. You can do this in SQL or in the Snowsight GUI.
CREATE POSTGRES INSTANCE MY_PG_INSTANCE
COMPUTE_FAMILY = 'BURST_S'
STORAGE_SIZE_GB = 10
AUTHENTICATION_AUTHORITY = POSTGRES
POSTGRES_VERSION = 17
COMMENT = 'Backend DB for App Runtime app';
When it's created, the connection info (password) for the snowflake_admin user is returned. This info is not shown again, so be sure to keep it somewhere safe. Creation takes a little while, so wait until the state becomes READY.
DESCRIBE POSTGRES INSTANCE MY_PG_INSTANCE
->> SELECT "value" AS state FROM $1 WHERE "property" = 'state';
Step 2: Check the Connection Info (Host Name)
The host name your app connects to is available via DESCRIBE POSTGRES INSTANCE.
DESCRIBE POSTGRES INSTANCE MY_PG_INSTANCE
->> SELECT "value" AS host FROM $1 WHERE "property" = 'host';
You'll get a host name like <instance-id>.<...>.us-west-2.aws.postgres.snowflake.app. Connections use port 5432 and SSL is required.
Prepare the table at this stage. There are two ways to do it.
-
Create it from a local PostgreSQL client (psql, etc.): In this case, you first need to allow your local IP in
POSTGRES_INGRESSthe same way as Step 8 (finish the network permission before connecting). -
Create it in the app's initialization or a migration: Have the app itself run
CREATE TABLEafter deploy.
-- Example: expense requests table
CREATE TABLE IF NOT EXISTS expense_requests (
id SERIAL PRIMARY KEY,
applicant TEXT NOT NULL,
department TEXT NOT NULL,
category TEXT NOT NULL,
amount INTEGER NOT NULL,
purpose TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Step 3: Store the Credentials in a Snowflake Secret
You want to avoid hardcoding the password in the app, so store the credentials in a Snowflake Secret. App Runtime can mount this Secret into the container via app.yml.
CREATE OR REPLACE SECRET APP_DB.APP.PG_CRED
TYPE = PASSWORD
USERNAME = 'snowflake_admin'
PASSWORD = '<YOUR_POSTGRES_PASSWORD>'
COMMENT = 'Snowflake Postgres credentials for the app';
For production, I'd recommend preparing a role/user with least-privilege access for the purpose at hand, and using those credentials.
Step 4: Create the egress Network Rule and EAI
So the app can reach out to the Snowflake Postgres host:port, create an egress network rule and an External Access Integration (EAI). Specify port 5432.
-- Network rule allowing egress to Snowflake Postgres
CREATE OR REPLACE NETWORK RULE APP_DB.APP.PG_EGRESS_RULE
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('<instance-host>.us-west-2.aws.postgres.snowflake.app:5432');
-- EAI bundling the rule above
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION APP_PG_EAI
ALLOWED_NETWORK_RULES = (APP_DB.APP.PG_EGRESS_RULE)
ENABLED = TRUE;
Note that since the credential Secret is mounted and read from app.yml in the next Step, you don't need to include the Secret in this EAI (ALLOWED_AUTHENTICATION_SECRETS). The EAI is solely responsible for permitting the egress path.
Step 5: Implement the App (the Connection Part)
On the app side, connect with the standard pg driver. Read the credentials from the Secret files mounted in Step 3 (/secrets/PG_CRED/username, /secrets/PG_CRED/password), and receive the host and port from environment variables — that keeps things easy to manage. SSL is required.
// lib/db.ts (excerpt of the connection part)
import { readFileSync } from "node:fs";
import { Pool } from "pg";
// Read credentials from the Secret mounted via app.yml
function readSecret(name: string): string {
return readFileSync(`/secrets/PG_CRED/${name}`, "utf8").trim();
}
export const pool = new Pool({
host: process.env.PGHOST,
port: Number(process.env.PGPORT ?? 5432),
user: readSecret("username"),
password: readSecret("password"),
database: process.env.PGDATABASE ?? "postgres",
ssl: { rejectUnauthorized: false }, // SSL required (equivalent to sslmode=require)
});
If you want stricter server certificate verification for SSL, you can specify the
certificatereturned byDESCRIBE POSTGRES INSTANCEas the CA.
The host, port, target DB, and the Secret to mount are specified in app.yml.
environment_variables:
- name: PGHOST
value: "<instance-host>.us-west-2.aws.postgres.snowflake.app"
- name: PGPORT
value: "5432"
- name: PGDATABASE
value: "postgres"
secrets:
- name: PG_CRED
secret: APP_DB.APP.PG_CRED
Step 6: Deploy
Deploy to App Runtime with snow app deploy. Uploading the source → remote build → creating the APPLICATION SERVICE all proceed automatically.
snow app deploy
Step 7: Attach the EAI to the Running App
This is the biggest point in this article. snowflake.yml has no field for specifying an EAI for the running app (the only thing you can specify is the build-time build_eai). So attach the EAI to the APPLICATION SERVICE created by the deploy, using ALTER APPLICATION SERVICE.
ALTER APPLICATION SERVICE APP_DB.APP.MY_APP
SET EXTERNAL_ACCESS_INTEGRATIONS = (APP_PG_EAI);
This setting takes effect on the running service, so the app can now resolve the Snowflake Postgres host name and attempt a connection.
If you don't know the service name (the deploy target), you can check it with:
SHOW APPLICATION SERVICES IN SCHEMA APP_DB.APP;
Step 8: Allow the Snowflake Postgres Ingress
Finally, allow inbound connections from the app on the Snowflake Postgres side. As we saw in the verification, the source IP is not fixed, so allow the egress IP range you get from SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES.
-- 1. Check the egress IP ranges
SELECT value:"ipv4_prefix"::VARCHAR AS ip_cidr_range
FROM TABLE(FLATTEN(INPUT => PARSE_JSON(SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES())));
-- 2. Create a Postgres ingress rule (specify the ranges from above)
CREATE OR REPLACE NETWORK RULE APP_DB.APP.PG_INGRESS_RULE
MODE = POSTGRES_INGRESS
TYPE = IPV4
VALUE_LIST = ('<CIDR range in my region>'); -- specify the fetched CIDR range
-- 3. Bundle it into a network policy
CREATE OR REPLACE NETWORK POLICY MY_PG_POLICY
ALLOWED_NETWORK_RULE_LIST = ('APP_DB.APP.PG_INGRESS_RULE');
-- 4. Attach it to the instance
ALTER POSTGRES INSTANCE MY_PG_INSTANCE SET NETWORK_POLICY = 'MY_PG_POLICY';
Because these ranges can change and expire, if the app suddenly can't connect one day, the first thing to check is re-fetching SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES and updating the network rule to the latest ranges.
Step 9: Verify It Works — Confirm Writes Land in Snowflake Postgres
At this point, the app can read from and write to Snowflake Postgres. Let's confirm that "the app's operations really do change the data in Snowflake Postgres."
First, submit a new expense request from the app's form (INSERT).
At that moment, on the Snowflake Postgres side, a record has actually been added. Peeking at the same table from outside App Runtime (e.g., a local client) shows the new row.
id | applicant | category | amount | status
----+--------------+----------+--------+---------
6 | Maria Lopez | Travel | 340 | pending ← the row submitted from the app
Next, let's "approve" this request in the app (UPDATE). The status on the Postgres side flips instantly.
id | applicant | status | updated_at
----+--------------+----------+------------
6 | Maria Lopez | approved | 12:32:55 ← updated by the approve action
Just like that, submissions and approvals in the App Runtime app are reflected directly in the Snowflake Postgres data. I confirmed that a "writable business app running right next to the data" is actually working.
Things to Watch Out For
Here are the points I found worth knowing up front so you don't stumble when you actually wire this up.
1. You can't set the running app's EAI in snowflake.yml
The only thing you can specify in snowflake.yml is the build-time build_eai. The egress EAI for the running app (Application Service) is attached after deploy with ALTER APPLICATION SERVICE ... SET EXTERNAL_ACCESS_INTEGRATIONS. This is an easy spot to mix up.
2. Connecting needs both egress and ingress permissions
Neither the egress (EAI) on the app side alone, nor the ingress (network policy) on the Postgres side alone, is enough. The connection is established only when both are set.
3. The source IP is not fixed
The App Runtime source IP is not fixed, so allow the range from SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES rather than an individual IP. The ranges have an expiration, so for production, set up a mechanism for periodic updates.
4. SSL is required
Connections to Snowflake Postgres require SSL. If SSL isn't enabled on the driver side, you can't connect.
5. An app deployed to a personal database can't be shared
If you don't specify a deploy target, the app may be placed in a personal database (USER$...) and can't be shared with other roles. If you have internal sharing in mind, specify a regular database/schema.
Conclusion
What did you think of the App Runtime × Snowflake Postgres integration?
Let's recap the key points of this article:
- With an App Runtime app + Snowflake Postgres, you can keep a full-fledged, write-heavy business app entirely within Snowflake's security perimeter
- The connection from the app to Snowflake Postgres is treated as egress, so an EAI is required (without it, even name resolution fails)
- The EAI for the running app is attached with
ALTER APPLICATION SERVICE, not insnowflake.yml - On the Postgres side, allow inbound connections with a
POSTGRES_INGRESSnetwork policy. Since the source IP is not fixed, allow the range fromSYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES - When building with CoCo, it goes smoothly if you convey the order — "create Postgres → build the app → create the EAI → attach it → allow ingress" — up front in the prompt
The configuration of "running a writable business app right next to your data" becomes a lot more realistic with this. It's still a new feature, and I think there's a lot you can do with it depending on your ideas. I'd love for you to get hands-on and share what you discover. Let's grow this new app-building experience together!
Promotion
Snowflake What's New Updates on X
I share Snowflake What's New updates on X. Follow for the latest insights:
English Version
Snowflake What's New Bot (English Version)
Japanese Version
Snowflake's What's New Bot (Japanese Version)
Change Log
(20260724) Initial post




zenn.dev
Top comments (0)