The Developer’s Docker Quick Reference: Running Backend Services in PowerShell

Docker has become the standard for creating clean, isolated, and reproducible development environments. By containerizing services like databases and message brokers, you can spin up an entire application stack in seconds and tear it down just as quickly, all without “polluting” your host operating system.

This guide is a quick-reference blog post, providing a curated set of PowerShell scripts to run common backend services.

Prerequisites: Docker Desktop and WSL2

All these commands assume you are running Docker Desktop for Windows. Docker Desktop uses the Windows Subsystem for Linux 2 (WSL2) as its backend, which is required to run the vast majority of official service images.

If you do not have WSL2 installed, you can typically install it and all its dependencies by running a single command in an Administrator PowerShell terminal and then restarting your machine:

PowerShell

wsl --install

A Note on PowerShell Syntax

  • Parameters: The scripts below use Param() blocks to define variables. You can run them as-is to use the default values, or you can pass in your own, e.g., .\Start-Postgres.ps1 -Password "mySecretPass"
  • Current Directory: We use the PowerShell variable ${PWD} to mount the current working directory.

Part 1: Anatomy of a docker run Command

Most service commands follow the same pattern. First, you create a “named volume” to persist data, and then you “run” an image, connecting it to that volume.

Here is a breakdown of the flags used:

  • docker volume create my_data: Creates a managed data volume. This is the key to persistence. Your data lives here, safe from container deletion.
  • docker run: The command to create and start a new container.
  • --name $ContainerName: Assigns a human-readable name you can use in other commands (e.g., docker stop $ContainerName).
  • -d: Runs the container in “detached” mode (in the background).
  • -p ${HostPort}:5432: Publishes a port in the format <HOST_PORT>:<CONTAINER_PORT>. This exposes the container’s internal port 5432 to localhost:$HostPort on your host machine.
  • -v ${VolumeName}:/var/lib/data: Mounts the volume. This links the $VolumeName volume you created to the /var/lib/data directory inside the container.
  • -e "PASSWORD=$Password": Sets an environment variable. This is the primary way to configure a container on startup (e.g., setting default passwords).
  • postgres:$ImageTag: The image:tag to run. Docker will pull this from Docker Hub if you don’t have it locally.

Part 2: The Service Cookbook (PowerShell Scripts)

Here are the copy-paste-ready scripts for common backend services.

1. PostgreSQL (Database)

  • Image: postgres:latest

PowerShell

# Create-PostgresVolume.ps1
docker volume create pc1_postgres_data

PowerShell

# Start-Postgres.ps1
Param(
    [string]$ContainerName = "pc1_postgres",
    [string]$Password = "admin",
    [int]$HostPort = 5432,
    [string]$VolumeName = "pc1_postgres_data",
    [string]$ImageTag = "latest"
)

# Note: See "Common Traps" section regarding the volume path for Postgres v18+.
docker run --name $ContainerName -e POSTGRES_PASSWORD=$Password -d -p ${HostPort}:5432 -v ${VolumeName}:/var/lib/postgresql postgres:$ImageTag
  • How to Access:
    • Host: localhost
    • Port: 5432
    • User: postgres
    • Password: admin (or your value for $Password)

2. pgAdmin 4 (Postgres Web UI)

  • Image: dpage/pgadmin4:latest

PowerShell

# Start-pgAdmin.ps1
Param(
    [string]$ContainerName = "pgadmin",
    [string]$Email = "admin@example.com",
    [string]$Password = "admin",
    [int]$HostPort = 8080,
    [string]$ImageTag = "latest"
)

# Note: See "Common Traps" section regarding connecting to Postgres.
docker run --name $ContainerName -p ${HostPort}:80 -e "PGADMIN_DEFAULT_EMAIL=$Email" -e "PGADMIN_DEFAULT_PASSWORD=$Password" -d dpage/pgadmin4:$ImageTag
  • How to Access:
    • Web UI: Open http://localhost:8080 in your browser.
    • Login Email: admin@example.com (or your value for $Email).
    • Login Password: admin (or your value for $Password).
  • Connecting to Postgres:
    1. In the pgAdmin UI, add a new server.
    2. In the “Connection” tab, set the Host to host.docker.internal. This special DNS name connects pgAdmin (from inside its container) to your host machine.
    3. Use the postgres user and admin password from Step 1.
    • (See Common Trap #2 for the recommended fix using a Docker network).

3. MariaDB (MySQL-compatible Database)

  • Image: mariadb:latest

PowerShell

# Create-MariaDBVolume.ps1
docker volume create mariadb_data

PowerShell

# Start-MariaDB.ps1
Param(
    [string]$ContainerName = "mariadb-dev",
    [string]$RootPassword = "YOUR_SECRET_PASSWORD",
    [int]$HostPort = 3306,
    [string]$VolumeName = "mariadb_data",
    [string]$ImageTag = "latest"
)

docker run --name $ContainerName -e MARIADB_ROOT_PASSWORD=$RootPassword -d -p ${HostPort}:3306 -v ${VolumeName}:/var/lib/mysql mariadb:$ImageTag
  • How to Access:
    • Host: localhost
    • Port: 3306
    • User: root
    • Password: YOUR_SECRET_PASSWORD

4. MySQL (Database)

  • Image: mysql:latest

PowerShell

# Create-MySQLVolume.ps1
docker volume create mysql_data

PowerShell

# Start-MySQL.ps1
Param(
    [string]$ContainerName = "mysql-dev",
    [string]$RootPassword = "YOUR_SECRET_PASSWORD",
    [int]$HostPort = 3307,
    [string]$VolumeName = "mysql_data",
    [string]$ImageTag = "latest"
)

# Note: Default host port is 3307 to avoid conflicts with MariaDB.
docker run --name $ContainerName -e MYSQL_ROOT_PASSWORD=$RootPassword -d -p ${HostPort}:3306 -v ${VolumeName}:/var/lib/mysql mysql:$ImageTag
  • How to Access:
    • Host: localhost
    • Port: 3307 (the host port we specified)
    • User: root
    • Password: YOUR_SECRET_PASSWORD

5. Microsoft SQL Server (Database)

  • Image: mcr.microsoft.com/mssql/server:2022-latest

PowerShell

# Create-MSSQLVolume.ps1
docker volume create mssql_data

PowerShell

# Start-MSSQL.ps1
Param(
    [string]$ContainerName = "mssql-dev",
    [string]$SAPassword = "Your_S@per_Str0ng_P@ssw0rd",
    [int]$HostPort = 1433,
    [string]$VolumeName = "mssql_data",
    [string]$ImageTag = "2022-latest"
)

# Note: ACCEPT_EULA=Y is required. See "Common Traps" for password persistence.
docker run --name $ContainerName -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$SAPassword" -d -p ${HostPort}:1433 -v ${VolumeName}:/var/opt/mssql mcr.microsoft.com/mssql/server:$ImageTag
  • How to Access (via Azure Data Studio or SSMS):
    • Server: localhost, 1433 (some clients prefer 127.0.0.1, 1433)
    • Authentication: SQL Login
    • User: sa
    • Password: Your_S@per_Str0ng_P@ssw0rd

6. MongoDB (NoSQL Database)

  • Image: mongo:latest

PowerShell

# Create-MongoVolume.ps1
docker volume create mongo_data

PowerShell

# Start-MongoDB.ps1
Param(
    [string]$ContainerName = "mongo-dev",
    [string]$RootUser = "YOUR_MONGO_USER",
    [string]$RootPassword = "YOUR_SECRET_PASSWORD",
    [int]$HostPort = 27017,
    [string]$VolumeName = "mongo_data",
    [string]$ImageTag = "latest"
)

# Note: See "Common Traps" for password persistence.
docker run --name $ContainerName -e MONGO_INITDB_ROOT_USERNAME=$RootUser -e MONGO_INITDB_ROOT_PASSWORD=$RootPassword -d -p ${HostPort}:27017 -v ${VolumeName}:/data/db mongo:$ImageTag
  • How to Access (e.g., MongoDB Compass):
    • Connection String: mongodb://YOUR_MONGO_USER:YOUR_SECRET_PASSWORD@localhost:27017/
    • (Env vars are optional but highly recommended)

7. Redis (In-Memory Store)

  • Image: redis:latest

PowerShell

# Create-RedisVolume.ps1
docker volume create redis_data

PowerShell

# Start-Redis-Persistent.ps1
Param(
    [string]$ContainerName = "redis-dev",
    [int]$HostPort = 6379,
    [string]$VolumeName = "redis_data",
    [string]$ImageTag = "latest"
)

# Appends "redis-server --save 60 1" to enable RDB snapshots for persistence.
docker run --name $ContainerName -d -p ${HostPort}:6379 -v ${VolumeName}:/data redis:$ImageTag redis-server --save 60 1
  • How to Access:
    • Host: localhost
    • Port: 6379
    • CLI:PowerShell
    • # Access-Redis-CLI.ps1 docker exec -it redis-dev redis-cli

8. RabbitMQ (Message Broker)

  • Image: rabbitmq:3-management

PowerShell

# Create-RabbitMQVolume.ps1
docker volume create rabbitmq_data

PowerShell

# Start-RabbitMQ.ps1
Param(
    [string]$ContainerName = "rabbitmq-dev",
    [int]$HostPortAMQP = 5672,
    [int]$HostPortMgmt = 15672,
    [string]$VolumeName = "rabbitmq_data",
    [string]$ImageTag = "3-management"
)

# Uses the ":management" tag to include the web UI.
docker run --name $ContainerName -d -p ${HostPortAMQP}:5672 -p ${HostPortMgmt}:15672 -v ${VolumeName}:/var/lib/rabbitmq/ rabbitmq:$ImageTag
  • How to Access:
    • Web UI: http://localhost:15672
    • Login: guest / guest
    • Application (AMQP): amqp://guest:guest@localhost:5672

9. Apache Kafka (Event Streaming)

  • Image: bitnami/kafka:latest

PowerShell

# Create-KafkaVolume.ps1
docker volume create kafka_data

PowerShell

# Start-Kafka-KRaft.ps1
Param(
    [string]$ContainerName = "kafka-dev",
    [int]$HostPort = 9092,
    [string]$VolumeName = "kafka_data",
    [string]$ImageTag = "latest"
)

# This complex command runs Kafka in Zookeeper-less (KRaft) mode.
# Note the use of "$ContainerName" and "$HostPort" in the environment variables.
docker run --name $ContainerName -p ${HostPort}:9092 -v ${VolumeName}:/bitnami/kafka -e KAFKA_CFG_NODE_ID=0 -e KAFKA_CFG_PROCESS_ROLES=controller,broker -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT -e "KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@${ContainerName}:9093" -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER -e "KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:$HostPort" -d bitnami/kafka:$ImageTag
  • How to Access:
    • Bootstrap Server: localhost:9092
  • How to Verify (from PowerShell):PowerShell
# Verify-Kafka-Create-Topic.ps1
docker exec -it kafka-dev kafka-topics.sh --create --topic my-test-topic --bootstrap-server localhost:9092

PowerShell

  • # Verify-Kafka-List-Topics.ps1 docker exec -it kafka-dev kafka-topics.sh --list --bootstrap-server localhost:9092

Part 3: The Node.js Development Workflow

This pattern is different. Instead of running a pre-built service, the goal is to create a live development environment for your own code.

The trick is to use two volumes:

  1. A bind mount (-v ${PWD}:/app) to map your local source code into the container for live-reloading.
  2. A named volume (-v ${VolumeName}:/app/node_modules) to “hide” the node_modules folder. This keeps your dependencies inside the container, preventing conflicts and improving performance.

PowerShell

# Create-NodeModulesVolume.ps1
docker volume create my_app_node_modules

PowerShell

# Start-Node-Dev-Shell.ps1
Param(
    [string]$ContainerName = "node-dev",
    [int]$HostPort = 3000,
    [string]$VolumeName = "my_app_node_modules",
    [string]$ImageTag = "22"
)

# Mounts the current directory (${PWD}) for live-reloading.
# Mounts a volume over node_modules for performance.
# This drops you into an interactive "bash" shell inside the container.
docker run -it --rm --name $ContainerName -p ${HostPort}:3000 -v ${PWD}:/app -v ${VolumeName}:/app/node_modules -w /app node:$ImageTag bash
  • How to Use:
    1. The command above will drop you into a bash shell inside the container (e.g., root@container-id:/app#).
    2. Inside the container shell, run npm install. Dependencies will install into the my_app_node_modules volume, not your local folder.
    3. Inside the container shell, run npm start (or npm run dev).
    4. On your host machine, open http://localhost:3000 to see your app.
    5. Edit code on your host. The changes will be reflected live inside the container.

Part 4: Essential Management & Troubleshooting

Running services is easy. Managing them is the next step.

Essential Management Commands

CommandPurpose
docker psLists all running containers.
docker ps -aLists all containers, including stopped ones.
docker logs <container_name>Shows the log output from a container. (e.g., docker logs mssql-dev).
docker exec -it <name> bashOpens an interactive bash shell inside a running container for debugging.
docker stop <container_name>Stops a running container.
docker rm <container_name>Removes a stopped container.
docker volume lsLists all your named volumes (where your data is).
docker volume pruneRemoves all unused volumes.

Common Trap #1: “The Password Won’t Change!”

You docker stop and docker rm your Postgres container, re-run the Start-Postgres.ps1 script with a new -Password "new_pass", but you can still only log in with the old password.

  • Why it happens: This is a feature, not a bug! Environment variables like POSTGRES_PASSWORD are only used to initialize the database the first time the container starts with an empty volume. Since your pc1_postgres_data volume still exists, the container starts up, sees the old data, and ignores the init variable.
  • The Fix: You must explicitly remove the volume to force a re-initialization.PowerShell
  • # Fix-Reset-Postgres.ps1 docker stop pc1_postgres docker rm pc1_postgres docker volume rm pc1_postgres_data # This is the step you were missing Now, your original Start-Postgres.ps1 script will work and set the new password.

Common Trap #2: “The ‘localhost’ Connection Fails”

You have pgAdmin and Postgres containers running. You go to the pgAdmin UI and try to connect to the Postgres host localhost or 127.0.0.1, but it fails with “Connection refused.”

  • Why it happens: From inside a container (like the pgAdmin container), localhost refers to the container itself, not your host machine. When you typed localhost, you told pgAdmin to look for a Postgres database inside its own container, which doesn’t exist.
  • The Quick Fix: Use host.docker.internal as the hostname. This is a special DNS name that Docker provides inside containers to resolve to the host machine’s IP address.
  • The Better Fix (Docker Networking): The more robust way is to create a user-defined Docker network. Containers on the same network can resolve each other by their --name.PowerShell
  • # Fix-Create-Docker-Network.ps1 docker network create my-dev-network # Then, re-run your start scripts adding the network flag: #.\Start-Postgres.ps1 --network my-dev-network #.\Start-pgAdmin.ps1 --network my-dev-network Now, in the pgAdmin UI, you can set the Host to pc1_postgres (the container’s name), and it will connect.

Common Trap #3: “Postgres Fails to Start (v18+)”

You run the Start-Postgres.ps1 script, but the container stops immediately. The logs show an error mentioning pg_ctlcluster and “unused mount/volume” at /var/lib/postgresql/data.

  • Why it happens: The postgres:latest image (version 18 and newer) changed how it stores data. It now requires the volume to be mounted at the parent directory, /var/lib/postgresql, so it can create version-specific subfolders (like /18/docker) inside the volume. Mounting directly to the old /data subfolder is no longer supported and causes the container to exit.
  • The Fix: Your Start-Postgres.ps1 script must use -v ${VolumeName}:/var/lib/postgresql (without the /data at the end), as shown in the example. If you have an old volume formatted the wrong way, you must first remove it:PowerShell
# Fix-Reset-Bad-Postgres-Volume.ps1
docker rm pc1_postgres
docker volume rm pc1_postgres_data

Then, run the Create-PostgresVolume.ps1 and Start-Postgres.ps1 scripts again.