Manoj
←Back to Blog
·4 min read

Docker for Local Development: A Setup You Can Keep Simple

Use Docker Compose for a reproducible local database without turning everyday development into an infrastructure project.

dockerdeveloper-experiencepostgresqllocal-development

Docker for Local Development: A Setup You Can Keep Simple

A development environment should make starting work easier. If opening a project requires remembering a database installation guide, repairing local version conflicts, and asking someone which port they used, the environment has become part of the problem.

Docker can help without containerizing every tool you use. A good starting point is to keep the application on your machine and run its external dependencies through Compose.

Containerize the dependency that causes friction

For a Node.js application, the first useful container is often PostgreSQL. Everyone gets the same database major version and a predictable startup command while keeping their preferred editor, debugger, and package manager.

You can move the application into a container later if environment parity or onboarding requirements justify it. Doing both at once makes it harder to tell whether a problem belongs to the application, the network, or the container configuration.

Write down the intended boundary: Node runs on the host, PostgreSQL runs in Compose, and the connection uses a local port.

Begin with a small Compose file

This example uses the official PostgreSQL 17 image deliberately. It is a development database with disposable credentials, not a production deployment template.

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: local-development-only
      POSTGRES_DB: app
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  postgres-data:

Start it with docker compose up -d db, then run migrations and the application using the project's normal commands. Confirm that the selected database version matches what the application supports rather than selecting an image tag by habit.

The loopback port binding is intentional. The host application can connect without publishing the database on every host network interface.

Know which hostname belongs to which process

A process running on your laptop connects to the published port through localhost. A containerized application in the same Compose network connects to the service name, such as db, and the container port.

Inside an application container, localhost refers to that container itself. It does not refer to the database container or automatically refer to your laptop. This small distinction explains a surprising number of connection failures.

Keep connection examples in the README for the chosen setup. If the team supports both host-based and container-based application development, show the two connection strings explicitly instead of relying on everyone to infer the difference.

Readiness is different from startup

A container can be running before its database is ready to accept connections. Add a health check and make application startup tolerate temporary dependency failures.

If the application also runs in Compose, depends_on with a service_healthy condition can wait for the database health check. Docker explains the distinction in its startup-order documentation.

A host-based application is outside that dependency graph, so its startup process must wait or retry separately. Do not replace a readiness check with a fixed sleep that only works on one developer's machine.

Treat local data as a separate lifecycle

The named volume keeps database files separate from the container. Recreating the container does not necessarily reset the database. Docker describes persistence and reuse in its volume guide.

Document how to stop services, inspect logs, and reset data. Make destructive reset commands visibly separate from everyday startup instructions. A developer should never discover that a routine cleanup removed the dataset they were using to reproduce a bug.

Keep migrations in version control and use repeatable seed data. A working database volume is not a substitute for a reproducible schema. Test a clean setup occasionally to catch assumptions that an old local volume hides.

Optimize for the next person opening the project

A useful onboarding checklist is short: install prerequisites, start dependencies, set local configuration, run migrations, seed sample records, and start the application.

Keep real credentials and production datasets out of that path. Add troubleshooting for port conflicts and readiness failures rather than adding unnecessary orchestration.

The result should be a development environment you can explain in a few sentences. Docker earns its place when it removes setup decisions and lets the team return to building the application.