DEV Community

Tsubasa Kanno
Tsubasa Kanno

Posted on

Give Your Snowflake App Runtime App a Real Backend with Snowflake Postgres

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.

The completed Expense Approval Workflow app — KPI cards and color-coded 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"}
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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:

  1. The egress permission on the app side (EAI)
  2. 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())));
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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_INGRESS the 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 TABLE after 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()
);
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)
});
Enter fullscreen mode Exit fullscreen mode

If you want stricter server certificate verification for SSL, you can specify the certificate returned by DESCRIBE POSTGRES INSTANCE as 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 in snowflake.yml
  • On the Postgres side, allow inbound connections with a POSTGRES_INGRESS network policy. Since the source IP is not fixed, allow the range from SYSTEM$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

Original Japanese Article

Top comments (0)