Skip to content

Database Setup

With the server routing in place, we need a PostgreSQL database to back it. We'll run Postgres locally using Docker Compose and keep the connection credentials in a .env file.

Two new files at the project root:

sh
doable/
├── .env          # database credentials
└── compose.yml   # runs Postgres in Docker

Install Docker

Docker Desktop bundles the Docker engine and the Compose plugin, and is the simplest option on any platform. The standalone Docker Engine plus the Compose plugin is a lighter alternative on Linux.

sh
brew install --cask docker
sh
# Docker Engine — follow https://docs.docker.com/engine/install/
# Compose plugin — follow https://docs.docker.com/compose/install/linux/
sh
# Download and run Docker Desktop from
# https://docs.docker.com/desktop/install/windows-install/

The official install guide covers more options. Verify both the engine and the Compose plugin are available:

sh
docker --version
docker compose version

Environment Variables

Create a .env file at the project root to hold the database credentials:

sh
# .env

# Database
PGHOST=db
PGPORT=5432
PGDATABASE=doable-dev
PGUSER=doable-user-dev
PGPASSWORD=doable-dev-p@ssw0rd

The variable names follow the standard libpq environment variables, which Postgres clients pick up automatically — including Squirrel, the Gleam package we'll use later to query the database.

DANGER

Never commit .env to version control. For this guide I did it intentionally — the credentials are for a local dev database only and keeping the file in the repo simplifies following along.

Docker Compose

Create compose.yml at the project root[1]:

yaml
# compose.yml

name: doable-dev

services:
  db:
    image: postgres:18-alpine
    restart: unless-stopped
    shm_size: 128mb
    environment:
      POSTGRES_PORT: ${PGPORT}
      POSTGRES_USER: ${PGUSER}
      POSTGRES_PASSWORD: ${PGPASSWORD}
      POSTGRES_DB: ${PGDATABASE}
    ports:
      - ${PGPORT}:${PGPORT}
    volumes:
      - data:/var/lib/postgresql

volumes:
  data:
    name: doable-dev-data

networks:
  default:
    name: doable-dev-network

A few things worth noting:

  • image: postgres:18-alpine — the Alpine variant keeps the image small.
  • shm_size: 128mb — Postgres uses shared memory for internal buffers; the Docker defaults to 64 MB but I followed the recommended settings in official Postgres image docs.
  • restart: unless-stopped — the container restarts automatically after a machine reboot, unless you explicitly stop it.
  • ${PGPORT}:${PGPORT} — Docker Compose reads .env automatically, so the port mapping uses the same variable as the app.
  • Named volume doable-dev-data — data persists across container restarts and rebuilds.
  • Named network doable-dev-network — an explicit name makes it easier to connect other services later.

Starting the Database

sh
docker compose up -d

The -d flag runs the containers in the background. On first run Docker will pull the Postgres image, which may take a moment.

To verify the container is healthy:

sh
docker compose ps

You should see db listed with status running.

sh
docker ps -a
# CONTAINER ID   IMAGE                COMMAND                  CREATED          STATUS          PORTS                                         NAMES
# 52bcc6e06f8a   postgres:18-alpine   "docker-entrypoint.s…"   11 seconds ago   Up 11 seconds   0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp   doable-dev-db-1

Verifying the Database

Connect to the running container with psql to confirm the database was created and is accepting connections:

sh
docker compose exec db psql -U doable-user-dev -d doable-dev

If everything is working you'll land in the psql prompt:

psql (18.x)
Type "help" for help.

doable-dev=#

Once inside, run \l to list databases and confirm doable-dev is present. Then run a quick query to confirm the database is accepting connections:

sql
SELECT 1;

You should see:

 ?column?
----------
        1
(1 row)

Type \q to exit.

TIP

Since the port is mapped to your host, you can also connect directly from your machine without going through Docker:

sh
psql -h localhost -p 5432 -U doable-user-dev -d doable-dev

The same credentials work in any GUI client — DBeaver, DataGrip, or similar — using localhost:5432.

What's Next

Postgres is running but empty — no tables, no columns. Next, we'll define the tasks schema as a reversible SQL migration and let a migrate service run it automatically whenever the stack starts.


  1. See commit d8bce07 on GitHub ↩︎