# Add Postgres to an app

> Declare a Postgres instance, link it into an app as DATABASE_URL, and verify from inside the app

URL: https://pier.run/docs/guides/add-postgres-to-an-app

**Goal.** `api` reads a `main` Postgres in the same environment through `DATABASE_URL`,
with no credentials in `pier.yaml`.

### Declare the instance

```yaml title="pier.yaml"
environments:
  prod:
    postgres:
      - name: main
        targets:
          - name: primary
            version: "17"
            plan: pg-s
            bootstrap_default_database: app
```

`pier apply` provisions it; `pier ls pg` shows `provisioning` for about a minute, then
`running`. From the CLI instead: `pier add pg --name main --plan pg-s --default-db app`.

```bash
pier ls pg
```

```text title="output"
                                   
 NAME VERSION PLAN STATUS  UPDATED 
                                   
 main 17      pg-s running 1m ago  
                                   

```

### Link it into the app

```bash
pier vars set DATABASE_URL --from postgres.main.url --app api
```

```text title="output"
→  Updated pier.yaml

  Changes:
    + create env_var "DATABASE_URL" in prod (from: postgres.main.url)

OK  Set DATABASE_URL on api in prod

```

`--from` stores a reference, not a value. The URL is resolved when the target starts, so
a password rotation or a plan change does not change the configuration. Deploy the app so
the running container receives it; the link is a variable, so no rebuild is needed:

```bash
pier deploy api
```

```text title="output"
→  Comparing prod environment against platform state...

  Changes:
    ~ update app "api" in prod
      env_version: 2 → 3  (env var — next deploy)

  Deploy:
    app "api"    deploy (deployed inline)

  Env vars:
    app "api"    1 pending

OK  Applied 1 config change(s) to prod
→  Waiting for infrastructure provisioning...
OK  Infrastructure ready

OK  Summary: 1 deployed

```

### Verify

From inside the app:

```bash
pier ssh api -- env | grep ^DATABASE_URL=
```

```text title="output"
DATABASE_URL=postgres://app:…@4b5nx4s2r4a.pg.pier.run:5432/app?sslmode=require

```

From your laptop:

```bash
pier psql main -- -c 'select version()'
```

```text title="output"
→  Connecting as cli_admin_e9187807 (role: pier_admin, expires 15:32 UTC)
                                                          version                                                           
----------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 17.9 (Debian 17.9-1.pgdg11+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
(1 row)


```

## What changed

```diff title="pier.yaml"
 environments:
   prod:
     apps:
       - name: api
         build_context: ./api
+        env:
+          DATABASE_URL:
+            from: postgres.main.url
+    postgres:
+      - name: main
+        targets:
+          - name: primary
+            version: "17"
+            plan: pg-s
+            bootstrap_default_database: app
```

## Variations

* **Individual fields** instead of a URL: `from: postgres.main.host`, `.port`, `.user`,
  `.password`, `.database`.
* **A separate instance for staging**: repeat the `postgres` entry under
  `environments.staging`; environments never share an instance.
* **Schema**: [Run database migrations](/docs/guides/run-database-migrations).

## See also

[Postgres](/docs/resources/postgres) · [`pier pg`](/docs/cli/reference/pg) ·
[`pier vars`](/docs/cli/reference/vars) · [Use Postgres from your laptop](/docs/guides/use-postgres-locally)
