> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Use Harlequin SQL IDE with Upsun

> Learn two ways to connect Harlequin SQL IDE to your Upsun PostgreSQL database: using local SSH tunnels or deploying Harlequin directly on the platform.


export const PostMeta = ({data = {}}) => {
  const {author, date} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "maz-mohammadi": {
      "name": "Maz Mohammadi"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
    </div>;
};

<PostMeta data={{ author: ["gmoigneu"], date: "2026-01-14T14:00:00.000Z", image: "/images/posts/hands-on/how-to-use-harlequin-sql-ide-with-upsun/harlequin-interface.webp" }} />

The Upsun CLI includes `upsun sql`, a quick way to run queries against your database from the command line. But sometimes you want more than a basic prompt. You want autocomplete, syntax highlighting, a visual schema browser, and the ability to scroll through large result sets. That's where [Harlequin](https://harlequin.sh/) comes in.

Harlequin is a terminal-based (TUI) SQL IDE that brings a rich, interactive experience to your shell. It includes a data catalog for browsing tables and columns, a query editor with autocompletion, and a results viewer that handles millions of rows. All without leaving your terminal.

This tutorial covers two ways to use Harlequin with your Upsun PostgreSQL database:

1. **Run Harlequin locally** and connect through an SSH tunnel
2. **Install Harlequin on your Upsun environment** and access it via SSH

Both approaches give you the same powerful interface. Choose the one that fits your workflow.

<img src="https://mintcdn.com/upsun-c9761871/tziXiwEbbKjwbX3l/images/posts/hands-on/how-to-use-harlequin-sql-ide-with-upsun/harlequin-interface.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=5fe5d841457ebc2cc5e3db422be98abe" alt="Harlequin's interface showing the data catalog, query editor, and results pane" width="2066" height="1340" data-path="images/posts/hands-on/how-to-use-harlequin-sql-ide-with-upsun/harlequin-interface.webp" />

## Prerequisites

Before you start, make sure you have:

* An [Upsun account](https://upsun.com/) with a project deployed
* A PostgreSQL service configured in your `.upsun/config.yaml`
* The [Upsun CLI](https://docs.upsun.com/administration/cli.html) installed and authenticated
* For [Option 1](#option-1-run-harlequin-locally-with-an-ssh-tunnel): Python 3.10+ and [uv](https://docs.astral.sh/uv/) (or pip) installed locally

Your project should have a [PostgreSQL](https://docs.upsun.com/anchors/services/postgresql/) service defined. If you don't have one yet, add this to your `.upsun/config.yaml`:

```yaml {filename=".upsun/config.yaml",hl_lines=["1-3","8-9"],linenostart=1} theme={null}
services:
  postgresql:
    type: postgresql:16

applications:
  myapp:
    # ... your app config
    relationships:
      database: "postgresql:postgresql"
```

## Option 1: Run Harlequin locally with an SSH tunnel

This approach keeps Harlequin on your local machine and connects to Upsun through a secure SSH tunnel. It's ideal for day-to-day development when you want quick access to your database without modifying your deployment.

### Install Harlequin with the PostgreSQL adapter

Harlequin uses adapters to connect to different database types. Install both Harlequin and its PostgreSQL adapter:

```bash {filename="Terminal"} theme={null}
uv tool install 'harlequin[postgres]'
```

Alternatively, you can use pip:

```bash {filename="Terminal"} theme={null}
pip install harlequin harlequin-postgres
```

Or Homebrew on macOS:

```bash {filename="Terminal"} theme={null}
brew install harlequin
```

Verify the installation:

```bash {filename="Terminal"} theme={null}
harlequin --version
harlequin, version 2.5.0

Installed Adapters:
  - duckdb, version 2.5.0
  - sqlite, version 2.5.0
  - postgres, version 1.3.0
```

### Open an SSH tunnel to your Upsun services

The Upsun CLI can create [SSH tunnels](https://docs.upsun.com/anchors/ssh/tunnel/direct/) to all services in your environment. Run:

```bash {filename="Terminal"} theme={null}
upsun tunnel:open
```

You'll see output similar to:

```bash {filename="Terminal"} theme={null}
upsun tunnel:open
Enter a number to choose an app:
  [0] api
  [1] chainlit
  [2] next
 > 0

SSH tunnel opened to postgresql at: postgresql://main:main@127.0.0.1:30000/main
SSH tunnel opened to cache at: valkey://127.0.0.1:30001

Logs are written to: /Users/nls/.upsun-cli/tunnels.log

List tunnels with: upsun tunnels
View tunnel details with: upsun tunnel:info
Close tunnels with: upsun tunnel:close

Save encoded tunnel details to the PLATFORM_RELATIONSHIPS variable using:
  export PLATFORM_RELATIONSHIPS="$(upsun tunnel:info --encode)"
```

Note the connection details. In this example:

* **Host**: `127.0.0.1`
* **Port**: `30000`
* **Username**: `main`
* **Password**: `main`
* **Database**: `main`

Your values may differ depending on your configuration.

### Connect Harlequin to the tunnel

With the tunnel open, launch Harlequin using the connection details:

```bash {filename="Terminal"} theme={null}
harlequin -a postgres "postgres://main:main@127.0.0.1:30000/main"
```

You can also pass the connection parameters individually:

```bash {filename="Terminal"} theme={null}
harlequin -a postgres -h 127.0.0.1 -p 30000 -U main --password main -d main
```

Harlequin opens in your terminal, ready to explore your database.

### Explore your database

<img src="https://mintcdn.com/upsun-c9761871/tziXiwEbbKjwbX3l/images/posts/hands-on/how-to-use-harlequin-sql-ide-with-upsun/harlequin-tunnel.webp?fit=max&auto=format&n=tziXiwEbbKjwbX3l&q=85&s=379e558fa892f20a904e441794feaec0" alt="harlequin-tunnel" width="2468" height="1586" data-path="images/posts/hands-on/how-to-use-harlequin-sql-ide-with-upsun/harlequin-tunnel.webp" />

Once connected, you can:

* **Browse the data catalog**: The left sidebar shows all schemas, tables, and columns in your database
* **Write queries**: The center pane is a full-featured editor with syntax highlighting and autocomplete (press `Ctrl+Space` to trigger suggestions)
* **View results**: Execute queries with `Ctrl+Enter` and scroll through results in the bottom pane
* **Export data**: Select results and export to CSV, JSON, or Parquet formats

Press `F1` to see all keyboard shortcuts.

When you're done, close the tunnel:

```bash {filename="Terminal"} theme={null}
upsun tunnel:close
```

## Option 2: Install Harlequin on Upsun

If you prefer to keep your tools on the platform, or want consistent access for your team, you can install Harlequin directly in your Upsun environment. Since Harlequin is a terminal UI, you'll access it by [SSH-ing into your container](https://docs.upsun.com/anchors/ssh/tunnel/app/).

### Add Harlequin to your build hook

Modify your `.upsun/config.yaml` to install Harlequin during the build phase. Add the pip install command to your existing build hook:

```yaml {filename=".upsun/config.yaml",hl_lines=["4-6","10"],linenostart=1} theme={null}
applications:
  myapp:
    type: python:3.13
    dependencies:
      python3:
        uv: "*"
    hooks:
      build: |
        set -eux
        uv tool install --python 3.13 'harlequin[postgres]'
        # Add your other build commands here

    relationships:
      database: "postgresql:postgresql"

    # ... rest of your config
```

If your application isn't Python-based, you can still install Harlequin. Python is available in most Upsun containers. Add a build hook that installs it:

```yaml {filename=".upsun/config.yaml",hl_lines=["4-6","10"],linenostart=1} theme={null}
applications:
  myapp:
    type: nodejs:22  # or php:8.3, etc.
    dependencies:
      python3:
        uv: "*"
    hooks:
      build: |
        set -eux
        uv tool install --python 3.13 'harlequin[postgres]'
        # Your other build commands

    relationships:
      database: "postgresql:postgresql"

    # ... rest of your config
```

### Deploy your changes

Commit and push your configuration changes:

```bash {filename="Terminal"} theme={null}
git add .upsun/config.yaml
git commit -m "Add Harlequin SQL IDE"
upsun push
```

Wait for the deployment to complete.

### SSH into your environment

Connect to your running container:

```bash {filename="Terminal"} theme={null}
upsun ssh
```

### Launch Harlequin

Upsun generate one environment variable for the database DSN automatically (see [Service Environment variables](https://docs.upsun.com/anchors/variables/environment/service/)):

```bash {filename="Terminal"} theme={null}
web@api.0:~$ env | grep DATABASE
DATABASE_URL=postgresql://main:main@postgresql.internal:5432/main
```

The name can vary as it is using the `relationships` name in the `myapp` section of the `.upsun/config.yaml` file (e.g. `database:` in the example above)

You can now launch Harlequin:

```bash {filename="Terminal"} theme={null}
/app/.local/bin/harlequin -a postgres $DATABASE_URL
```

If you want to avoid specifying the whole path, update your `PATH` variable in the deploy hook of your application:

```yaml {filename=".upsun/config.yaml",hl_lines=["4"],linenostart=1} theme={null}
hooks:
  deploy: |
    set -x -e
    export PATH="$PATH:/app/.local/bin/"
```

## Tips and best practices

### Create a shell alias for quick access

If you use Option 1 frequently, add an alias to your shell configuration:

```bash {filename="~/.bashrc or ~/.zshrc"} theme={null}
alias upsun-harlequin='upsun tunnel:open && harlequin -a postgres "$(upsun tunnel:info --property=database.0.url)"'
```

### Use read-only credentials for safety

When exploring production data, consider creating a read-only PostgreSQL user to prevent accidental modifications. You can configure additional database endpoints in your services configuration.

### Keyboard shortcuts

Here are the most useful Harlequin shortcuts:

| Action               | Shortcut     |
| -------------------- | ------------ |
| Execute query        | `Ctrl+Enter` |
| Autocomplete         | `Ctrl+Space` |
| New query tab        | `Ctrl+N`     |
| Close tab            | `Ctrl+W`     |
| Focus data catalog   | `Ctrl+B`     |
| Help / all shortcuts | `F1`         |
| Quit                 | `Ctrl+Q`     |

### Query history

Harlequin automatically saves your query history. Press `F8` to browse previous queries and re-run them.

## Conclusion

You now have two ways to use Harlequin with your Upsun PostgreSQL database:

1. **Local installation with SSH tunnel**: Best for individual developers who want quick, on-demand access
2. **Installed on Upsun**: Best for team environments or when you want tools available directly on the platform

Both approaches give you a powerful, visual way to explore and query your data without leaving the terminal.

For more Harlequin features and configuration options, check out the [official documentation](https://harlequin.sh/docs/getting-started).

Ready to try it out? [Create a free Upsun account](https://upsun.com/) and deploy your first PostgreSQL-powered application.
