cyberangles guide

PostgreSQL and Docker: A Seamless Setup for Modern Applications

In the era of cloud-native development and microservices, consistency, scalability, and isolation are paramount. PostgreSQL, the robust open-source relational database, and Docker, the leading containerization platform, together form a powerful duo to meet these needs. Whether you’re a developer setting up a local environment, a DevOps engineer deploying to production, or a startup scaling infrastructure, combining PostgreSQL with Docker simplifies setup, ensures environment parity, and accelerates development workflows. This blog will guide you through a step-by-step setup of PostgreSQL using Docker, covering everything from basic container deployment to advanced configurations like data persistence, custom settings, and integration with Docker Compose. By the end, you’ll have a production-ready PostgreSQL environment that’s portable, secure, and easy to manage.

Table of Contents

  1. Understanding PostgreSQL and Docker
  2. Prerequisites
  3. Step 1: Installing Docker
  4. Step 2: Pulling the Official PostgreSQL Docker Image
  5. Step 3: Running a PostgreSQL Container
  6. Step 4: Ensuring Data Persistence with Docker Volumes
  7. Step 5: Configuring PostgreSQL Environment Variables
  8. Step 6: Connecting to the PostgreSQL Container
  9. Advanced Setup: Custom PostgreSQL Configuration
  10. Using Docker Compose for Multi-Service Applications
  11. Best Practices for PostgreSQL with Docker
  12. Troubleshooting Common Issues
  13. Conclusion
  14. References

Understanding PostgreSQL and Docker

What is PostgreSQL?

PostgreSQL (often called “Postgres”) is an enterprise-grade open-source relational database management system (RDBMS) known for its ACID compliance, extensibility, and support for advanced data types (e.g., JSON, arrays, geospatial data). It’s widely used in applications ranging from small projects to large-scale enterprise systems.

What is Docker?

Docker is a platform for developing, shipping, and running applications in containers—lightweight, standalone executable packages that include everything needed to run an app: code, runtime, libraries, and settings. Containers isolate applications from their environment, ensuring they work seamlessly across different stages (development, testing, production).

Why Combine PostgreSQL and Docker?

  • Environment Consistency: Avoid “it works on my machine” issues by packaging PostgreSQL and its dependencies into a container that runs identically everywhere.
  • Isolation: Run multiple PostgreSQL instances (with different versions or configurations) on the same host without conflicts.
  • Simplified Setup: Skip manual installation, configuration, and dependency management—spin up a PostgreSQL instance in seconds with a single command.
  • Scalability: Easily replicate containers for load balancing or CI/CD pipelines.
  • Portability: Deploy PostgreSQL containers to any cloud provider, on-premises server, or Kubernetes cluster with minimal changes.

Prerequisites

Before starting, ensure you have the following:

  • A computer running Windows, macOS, or Linux.
  • Docker Engine or Docker Desktop installed (see Step 1).
  • Basic familiarity with the command line.
  • Optional: psql (PostgreSQL client) installed locally for testing connections (or use docker exec as shown later).

Step 1: Installing Docker

Docker provides installers for all major operating systems. Follow the official guides below:

Windows

macOS

  • Install Docker Desktop for Mac. Check compatibility (requires macOS 10.15+ for Intel, macOS 11+ for Apple Silicon).

Linux

  • Install Docker Engine (e.g., for Ubuntu: sudo apt-get install docker-ce docker-ce-cli containerd.io).
  • Add your user to the docker group to run Docker commands without sudo:
    sudo usermod -aG docker $USER  
    (Log out and back in for changes to take effect.)

Verify installation with:

docker --version  
docker run hello-world  # Runs a test container to confirm Docker works  

Step 2: Pulling the Official PostgreSQL Docker Image

Docker Hub hosts the official PostgreSQL image, maintained by the PostgreSQL team. To pull the latest version:

docker pull postgres  

Specifying a Version

For production, always use a specific version tag (e.g., 16, 16-alpine for a smaller Alpine Linux-based image):

docker pull postgres:16-alpine  # Lightweight version (Alpine Linux)  
docker pull postgres:16         # Standard Debian-based image  

Check downloaded images with:

docker images | grep postgres  

Step 3: Running a PostgreSQL Container

To start a PostgreSQL container, use the docker run command. Let’s begin with a basic example:

docker run -d \  
  --name my-postgres \  
  -e POSTGRES_PASSWORD=mysecretpassword \  
  -e POSTGRES_USER=myuser \  
  -e POSTGRES_DB=mydb \  
  -p 5432:5432 \  
  postgres:16-alpine  

Breaking Down the Command

  • -d: Runs the container in “detached” mode (background).
  • --name my-postgres: Assigns a name to the container (easier to reference later).
  • -e: Sets environment variables (critical for configuring PostgreSQL).
  • -p 5432:5432: Maps port 5432 on the host to port 5432 in the container (PostgreSQL’s default port).
  • postgres:16-alpine: The image to use.

Verify the Container is Running

docker ps  # Lists running containers  
# Look for "my-postgres" in the output  

If the container isn’t running, check logs for errors:

docker logs my-postgres  

Step 4: Ensuring Data Persistence with Docker Volumes

Containers are ephemeral—data stored inside is lost when the container is deleted. To persist data, use Docker Volumes.

Named volumes are managed by Docker and persist independently of containers.

  1. Create a named volume:

    docker volume create pgdata  
  2. Run the container with the volume mounted to PostgreSQL’s data directory (/var/lib/postgresql/data):

    docker run -d \  
      --name my-postgres \  
      -e POSTGRES_PASSWORD=mysecretpassword \  
      -e POSTGRES_USER=myuser \  
      -e POSTGRES_DB=mydb \  
      -p 5432:5432 \  
      -v pgdata:/var/lib/postgresql/data \  # Mount the named volume  
      postgres:16-alpine  

Option 2: Bind Mounts (For Development)

Bind mounts map a local directory on your host to the container, useful for development (e.g., syncing config files).

# Create a local directory for data  
mkdir -p ~/postgres-data  

# Run with bind mount  
docker run -d \  
  --name my-postgres \  
  -e POSTGRES_PASSWORD=mysecretpassword \  
  -p 5432:5432 \  
  -v ~/postgres-data:/var/lib/postgresql/data \  # Bind mount local dir  
  postgres:16-alpine  

Note: Bind mounts may have permission issues on Linux/macOS. Use chmod 777 ~/postgres-data (temporary fix for testing) or adjust ownership with --user.

Step 5: Configuring PostgreSQL Environment Variables

PostgreSQL uses environment variables to initialize the database. Here are key variables:

VariablePurpose
POSTGRES_PASSWORDRequired: Password for the default user (postgres or POSTGRES_USER).
POSTGRES_USERCustom username (default: postgres).
POSTGRES_DBName of the default database (default: value of POSTGRES_USER).
POSTGRES_INITDB_ARGSArguments for initdb (e.g., --encoding=UTF8 --lc-collate=C).
PGDATACustom data directory (default: /var/lib/postgresql/data).

Example: Custom User, DB, and Encoding

docker run -d \  
  --name my-postgres \  
  -e POSTGRES_USER=appuser \  
  -e POSTGRES_PASSWORD=securepass123 \  
  -e POSTGRES_DB=appdb \  
  -e POSTGRES_INITDB_ARGS="--encoding=UTF8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8" \  
  -v pgdata:/var/lib/postgresql/data \  
  -p 5432:5432 \  
  postgres:16-alpine  

Step 6: Connecting to the PostgreSQL Container

There are two common ways to connect to the running PostgreSQL instance:

Method 1: Using docker exec (Container Shell)

Access the container’s shell and run psql (PostgreSQL’s CLI) directly:

# Get a bash shell in the container  
docker exec -it my-postgres bash  

# Inside the container, connect to PostgreSQL  
psql -U appuser -d appdb  

You’ll see the PostgreSQL prompt: appdb=#. Test with a query:

CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(50));  
INSERT INTO users (name) VALUES ('Alice'), ('Bob');  
SELECT * FROM users;  

Method 2: Connecting from Host (Local Client)

If you have psql installed locally, connect via localhost:5432:

psql -h localhost -p 5432 -U appuser -d appdb  

Enter the password when prompted.

Using pgAdmin (GUI Tool)

For a graphical interface:

  1. Install pgAdmin.
  2. Add a new server with:
    • Hostname: localhost
    • Port: 5432
    • Username: appuser (or your custom user)
    • Password: securepass123

Advanced Setup: Custom PostgreSQL Configuration

To override default PostgreSQL settings (e.g., max_connections, shared_buffers), mount a custom postgresql.conf file.

Step 1: Create a Custom Config File

On your host, create postgresql.conf (e.g., in ./config):

# Example custom config  
max_connections = 100  
shared_buffers = 256MB  # 25% of RAM for dedicated DB (adjust for your host)  
log_statement = 'all'   # Log all SQL statements (for debugging)  

Step 2: Mount the Config File

Run the container with a bind mount for the config:

docker run -d \  
  --name my-postgres \  
  -e POSTGRES_PASSWORD=mysecretpassword \  
  -v pgdata:/var/lib/postgresql/data \  
  -v ./config/postgresql.conf:/etc/postgresql/postgresql.conf \  # Custom config  
  -p 5432:5432 \  
  postgres:16-alpine -c 'config_file=/etc/postgresql/postgresql.conf'  

The -c 'config_file=...' flag tells PostgreSQL to use your custom config.

Using Docker Compose for Multi-Service Applications

Docker Compose simplifies managing multi-container apps (e.g., PostgreSQL + a web app). Create a docker-compose.yml file:

version: '3.8'  

services:  
  postgres:  
    image: postgres:16-alpine  
    container_name: my-postgres  
    environment:  
      POSTGRES_USER: appuser  
      POSTGRES_PASSWORD: securepass123  
      POSTGRES_DB: appdb  
    volumes:  
      - pgdata:/var/lib/postgresql/data  
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf  # Optional custom config  
    ports:  
      - "5432:5432"  
    restart: unless-stopped  # Auto-restart on failure  

  # Example: Add a web app service (e.g., Node.js)  
  webapp:  
    build: ./webapp  # Path to your app's Dockerfile  
    depends_on:  
      - postgres  # Ensures postgres starts first  
    environment:  
      DATABASE_URL: postgresql://appuser:securepass123@postgres:5432/appdb  
    ports:  
      - "3000:3000"  

volumes:  
  pgdata:  # Named volume for PostgreSQL data  

Start the stack with:

docker-compose up -d  # -d for detached mode  

Other useful commands:

docker-compose logs postgres  # View PostgreSQL logs  
docker-compose down           # Stop and remove containers (data in volumes persists)  
docker-compose down -v        # Stop and delete volumes (CAUTION: Data loss!)  

Best Practices for PostgreSQL with Docker

  1. Use Specific Image Tags: Avoid latest; use postgres:16 to pin versions.

  2. Secure Passwords: Never hardcode passwords in commands/Compose files. Use Docker Secrets (Swarm) or .env files with Docker Compose:

    # Create .env file  
    POSTGRES_PASSWORD=securepass123  
    
    # In docker-compose.yml:  
    environment:  
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}  

    (Add .env to .gitignore!)

  3. Limit Resources: Restrict CPU/memory with --memory=2g --cpus=1 (or deploy.resources in Compose).

  4. Backup Regularly: Use pg_dump to back up data:

    docker exec my-postgres pg_dump -U appuser appdb > backup.sql  
  5. Monitor Containers: Use tools like Docker Stats (docker stats my-postgres) or Prometheus + Grafana.

  6. Avoid Running as Root: Run the container with a non-root user (see PostgreSQL image docs).

  7. Update Images: Regularly pull new versions to patch security vulnerabilities.

Troubleshooting Common Issues

Container Fails to Start

  • Check logs: docker logs my-postgres.
  • Common cause: Missing POSTGRES_PASSWORD (required).

Connection Refused

  • Ensure the container is running: docker ps.
  • Verify port mapping: docker port my-postgres (should show 5432/tcp -> 0.0.0.0:5432).
  • Check firewall rules blocking port 5432.

Data Loss

  • Did you use a volume? Without -v, data is lost when the container is deleted.

Permission Errors (Bind Mounts)

  • On Linux, run:
    sudo chown -R 999:999 ~/postgres-data  # 999 is the UID/GID of the postgres user in the container  

Conclusion

PostgreSQL and Docker together streamline database management for modern applications, offering consistency, isolation, and scalability. By following this guide, you’ve learned to:

  • Install Docker and pull the PostgreSQL image.
  • Run containers with persistent storage using volumes.
  • Configure environment variables and custom settings.
  • Connect to PostgreSQL via CLI or GUI tools.
  • Orchestrate multi-service apps with Docker Compose.

For production, extend this setup with Docker Swarm or Kubernetes for high availability, and always follow security best practices like encrypting data and restricting network access.

References