
September 09, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
You need a clean PostgreSQL instance on your laptop without installing server packages on the host. The fastest path is to run PostgreSQL in Docker for development: one container, a named volume for data, and environment variables your app already understands. This guide walks through a setup I use on Laravel and Symfony projects, with copy-paste configs that survive team onboarding and CI parity. If you already use Docker for apps, adding Postgres takes minutes—not a weekend of local DBA work. Start with our Docker installation guide for Ubuntu if the engine is not running yet.
postgres:18 image with a named volume, published port 5432, and a healthcheck so apps wait until the database accepts connections. Use Docker Compose to pin versions and share one .env file across the team.Why should you run PostgreSQL in Docker for local development?
Host-installed PostgreSQL works until it does not. Version drift, conflicting extensions, and leftover databases from old projects create friction. A container gives you PostgreSQL 18 today and PostgreSQL 17 tomorrow—without apt purge cycles.
On client projects I often maintain both MySQL and PostgreSQL apps. Docker keeps each project's database isolated. Your Laravel app on port 8000 talks to postgres-app-a. Your Symfony service talks to postgres-app-b. Neither touches the other's data directory.
Containers also mirror production more closely than XAMPP-style stacks. Managed hosts and VPS deployments run PostgreSQL as a dedicated service. Local Docker teaches the same connection string patterns: host, port, database, user, password. That alignment reduces "works on my machine" bugs during MySQL-to-PostgreSQL migrations.
Docker is not magic. You still need backups, sane credentials, and migration discipline. What changes is repeatability. A new developer runs docker compose up -d and gets the same major version you use in staging.
How do you start PostgreSQL with a single docker run command?
For a quick throwaway database, docker run is enough. Pin the major version. Never use the floating latest tag on shared projects.
Minimal one-liner
docker run -d \
--name dev-postgres \
-e POSTGRES_USER=app \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=app_dev \
-p 5432:5432 \
-v pgdata_dev:/var/lib/postgresql/data \
postgres:18 Connect from the host with any SQL client:
psql "postgresql://app:secret@127.0.0.1:5432/app_dev" Flags worth understanding:
-v pgdata_dev:/var/lib/postgresql/data— stores cluster files in a Docker-managed volume. Without it, data dies when the container is removed.-p 5432:5432— publishes the port to localhost. Skip this if only sibling containers need access.POSTGRES_*env vars — seed the superuser role and default database on first init only.
Verify the container is healthy before running migrations:
docker logs dev-postgres
docker exec dev-postgres pg_isready -U app -d app_dev Official image behaviour is documented on Docker Hub's PostgreSQL page. Read the "Environment Variables" section once. It saves hours of guessing.
What is the best Docker Compose setup for PostgreSQL development?
Real teams outgrow one-liners. Docker Compose gives you a file in git, repeatable service names, and health checks. Pair it with the patterns in our multi-container Compose guide for local development.
Recommended compose.yaml
services:
postgres:
image: postgres:18
container_name: app_postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: ${DB_USERNAME:-app}
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
POSTGRES_DB: ${DB_DATABASE:-app_dev}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-app} -d ${DB_DATABASE:-app_dev}"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pgdata: Start it:
docker compose up -d postgres
docker compose ps Wait until health shows healthy. Then run application migrations. On Laravel 13 with PHP 8.3+, your .env might look like this:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=app_dev
DB_USERNAME=app
DB_PASSWORD=secret When the app also runs in Compose, set DB_HOST=postgres instead. Docker DNS resolves the service name on the internal network. That pattern is covered in depth in Docker networking and volumes explained.
App + database in one Compose file
For full-stack local work, add your PHP app service:
services:
app:
build: .
depends_on:
postgres:
condition: service_healthy
environment:
DB_HOST: postgres
DB_CONNECTION: pgsql
volumes:
- .:/var/www/html
postgres:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app_dev"]
interval: 5s
retries: 10
volumes:
pgdata: Laravel Sail wraps a similar layout. If you standardise on Sail, read local Laravel dev with Sail and Docker alongside this page. Symfony projects follow the same Compose skeleton with different env var names.
How do docker run and Docker Compose compare for PostgreSQL dev?
Both launch the same image. The difference is workflow ergonomics and team scale.
| Criteria | docker run | Docker Compose |
|---|---|---|
| Reproducibility | Command history or shell scripts | Version-controlled YAML in the repo |
| Multi-service apps | Manual network and link flags | Built-in service DNS and depends_on |
| Health checks | Possible but verbose CLI | Declarative healthcheck block |
| Env management | Inline -e flags | .env interpolation |
| Teardown | docker rm -f per container | docker compose down |
| Best for | Quick experiments, one-off imports | Day-to-day team development |
Verdict: use docker run to test an extension or major version bump. Use Compose for anything that survives beyond an afternoon. Most production-minded teams pick Compose plus Compose profiles and overrides to split dev, test, and CI configs.
How do you connect Laravel, Symfony, and CLI tools to the container?
Connection rules depend on where the client process runs. The matrix is simple but easy to get wrong.
- App on host, DB in Docker —
DB_HOST=127.0.0.1and published port5432. - App in Docker, DB in Docker (same Compose project) —
DB_HOST=postgresusing the service name. - GUI client (DBeaver, pgAdmin, TablePlus) —
127.0.0.1:5432with the credentials from Compose env. - CI runner — service container named
postgreson the job network; no host port publish required.
For Laravel, install the PHP pgsql extension and confirm the driver:
php -m | grep pgsql
php artisan db:show Symfony uses Doctrine. Set DATABASE_URL in .env.local:
DATABASE_URL="postgresql://app:secret@127.0.0.1:5432/app_dev?serverVersion=18&charset=utf8" Our PostgreSQL guide for Laravel developers covers migrations, JSON columns, and indexing patterns beyond connection strings. If you are choosing between engines, compare PostgreSQL vs MySQL for production before you commit schema design.
On booking systems like Adventure Third Pole Trek, PostgreSQL handled relational booking data with strict constraints. Local Docker let us test concurrent reservation logic without touching shared staging data.
What are the common mistakes when running PostgreSQL in Docker locally?
Most failures I see in production debugging sessions start as innocent local shortcuts. Avoid these early.
Port 5432 already in use
Another Postgres instance—or a stale container—owns the port. Check with:
sudo ss -tlnp | grep 5432
docker ps --filter publish=5432 Remap the host port if needed: "5433:5432". Update DB_PORT=5433 in your app env.
Data loss after docker compose down
docker compose down -v deletes named volumes. That is correct for CI. It is catastrophic for a week's local seed data. Use plain down during normal work. Understand volume lifecycle in our volumes and networking article.
Wrong major version after image tag change
PostgreSQL cannot downgrade data directories. If you switch from 18 to 17, the container exits with a init error. Fix: backup with pg_dump, remove the volume, recreate on the target version. For production upgrade paths see PostgreSQL administration essentials.
Weak default passwords in shared repos
secret is fine for solo local work. Never reuse it in staging or production. Generate strong passwords with our password generator tool and keep secrets in .env files excluded from git.
Missing health checks cause race conditions
Migrations run before Postgres accepts connections. The app crashes once and sometimes leaves partial state. Add depends_on: condition: service_healthy or retry logic in entrypoint scripts.
Performance tuning on macOS and WSL2
Docker Desktop file sharing can slow heavy write workloads. For large local imports, prefer docker exec -i with psql or run bulk loads inside the container filesystem. On Linux natively, performance is close to bare metal for typical dev datasets.
Resource limits matter when you run multiple containers. See how to limit Docker container resources before your laptop fan becomes the team soundtrack.
How do you seed, back up, and reset your development database?
Development databases are disposable in theory. In practice you want fast reset without mystery state.
Dump and restore
docker exec app_postgres pg_dump -U app -d app_dev -Fc > backup.dump
docker exec -i app_postgres pg_restore -U app -d app_dev --clean --if-exists < backup.dump Custom format (-Fc) compresses well and restores selectively. Plain SQL dumps are easier to diff in git for small fixtures.
Reset everything
docker compose down
docker volume rm project_pgdata
docker compose up -d postgres
php artisan migrate:fresh --seed Document the reset command in your project README. Future you—and every contractor—will follow it.
Init scripts for extensions
Need uuid-ossp, pg_trgm, or PostGIS locally? Mount SQL into /docker-entrypoint-initdb.d/:
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/01-init.sql CREATE EXTENSION IF NOT EXISTS "pg_trgm";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; Init scripts run only on first volume creation. Changing them later requires a fresh volume or manual CREATE EXTENSION.
Validate JSON payloads during API work with the JSON formatter tool when debugging PostgreSQL JSONB columns alongside your HTTP layer.
Key Takeaways
- Pin
postgres:18(or your team's target major) and store config in Docker Compose—not ad hoc shell history. - Always use a named volume for
/var/lib/postgresql/data; bind mounts are rarely worth the permission pain on macOS. - Set
DB_HOST=127.0.0.1for host apps andDB_HOST=postgresfor containerised apps on the same Compose network. - Add a
healthcheckwithpg_isreadyso migrations never race a starting database. - Never run
docker compose down -vunless you intend to wipe local data completely. - Keep dev credentials in
.env, dump before major version changes, and match production extensions in init scripts.
People Also Ask
Can I run PostgreSQL in Docker for development on Windows?
Yes. Docker Desktop with WSL2 backend is the practical path on Windows 10 and 11. Install Docker inside Ubuntu on WSL2, publish port 5432, and connect from Windows tools via localhost. File performance is better when the project lives inside the WSL filesystem—not on C:\ mounted volumes.
Is PostgreSQL in Docker safe for production?
Docker is fine as a runtime wrapper in production when you operate it like any other Postgres deployment: persistent volumes, monitored backups, replication, and resource limits. This article targets local development only. Production topology belongs on managed services or carefully administered VMs—see the Linux system administration service for ops-heavy deployments.
Which PostgreSQL version should I use in 2026?
PostgreSQL 18 is the current major release. PostgreSQL 17 remains widely deployed and fully viable. Pick the version your staging and production environments use. Mixed majors between local and prod invite subtle compatibility surprises in queries and extensions.
Does Laravel Sail include PostgreSQL?
Sail supports PostgreSQL through Compose stubs. Run php artisan sail:install and select pgsql. Sail publishes a prewired Compose file similar to the examples here. It is the fastest Laravel-specific path if your whole team already standardises on Sail.
Ship your next project with confidence
You now have a repeatable way to run PostgreSQL in Docker for development: pinned images, persistent volumes, health checks, and connection rules that work for Laravel, Symfony, and GUI clients. Start with Compose in your repo today, align the major version with staging, and treat local data like it matters—because schema bugs caught early save production firefights later. Building something that needs PostgreSQL-backed workflows, booking logic, or a full enterprise application stack? Review our eCommerce portfolio work or contact us to talk through architecture before you commit to schema decisions.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

