Docker

Docker & Node.js Development Guide

A comprehensive breakdown of volume mapping, auto-reloading, and core Docker concepts.

The Complete Command

This is the command we used to run your Express server with live-reloading enabled:

docker run --name express-app-3001 --rm -p 3001:3000 -v /home/shaileshsonare/Desktop/docker:/app -w /app node:latest node --watch app.js

Docker Flags Explained

  • --name express-app-3001: Assigns a custom, memorable name to your container. Instead of Docker generating a random name, you can easily reference this container to stop it or view its logs (e.g., docker stop express-app-3001).
  • --rm: Automatically removes the container from your system as soon as it stops running. This keeps your system clean from old, stopped containers piling up and consuming disk space.
  • -p 3001:3000: Maps a port on your host machine to a port inside the container. Format: HOST_PORT:CONTAINER_PORT. We used 3001 on the host because 3000 was already in use. Your Node app inside the container listens on 3000, but you access it via http://localhost:3001.
  • -v /path/on/host:/app: Volume mounting. This mounts your local directory directly into the /app directory inside the container. Any changes you make locally are instantly available inside the container.
  • -w /app: Sets the “Working Directory”. It tells Docker to run all subsequent commands (like node app.js) from inside the /app directory.

Node.js Flags Explained

  • --watch: A built-in Node.js feature (introduced in v18.11). It tells the Node engine to actively monitor the executed file and its dependencies for any changes. When you hit “Save” in your editor, Node.js automatically restarts the server to reflect those changes.

Why did the volume seem like it wasn’t working initially?
The volume was always working perfectly—the file updated inside the container instantly. However, without the --watch flag, Node.js loads your code into memory once and stays running indefinitely. It was blissfully unaware that the file on disk had changed. Adding --watch bridges that gap.

Exploring Inside a Container (-it bash)

If you need to explore the files inside your container, install dependencies manually, or debug something, you can open an interactive terminal session inside the container.

docker run -it -v /home/shaileshsonare/Desktop/docker:/app -w /app node:latest bash
  • -it: This is actually two flags combined: -i (interactive, keeps standard input open) and -t (allocates a pseudo-TTY). Together, they allow you to interact with the container just like a normal terminal.
  • bash: Instead of running a Node command, this tells the container to start the Bash shell. You will immediately be dropped into a command prompt running inside the container.

Pro Tip: If your container is already running, you can jump into it without creating a new one by running: docker exec -it express-app-3001 bash

The Dockerfile

While running commands with flags is great for quick development, a Dockerfile acts as a blueprint. It allows you to write down exactly how to build a custom image containing your app, so you don’t have to map volumes or set working directories every time you deploy.

A typical Dockerfile for this project would look like this:

FROM node:latest
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]

With this file, you can bake your code into an image using docker build -t my-node-app . and then run it anywhere simply by using docker run -p 3001:3000 my-node-app.

Docker Compose

When your Docker run commands become extremely long (like ours did) or when you need to run multiple connected containers (like an Express app and a Database), you use Docker Compose.

You create a file named docker-compose.yml which stores all your flags and configurations in a clean, readable format:

version: '3.8'
services:
  web:
    image: node:latest
    container_name: express-app-3001
    ports:
      - "3001:3000"
    volumes:
      - .:/app
    working_dir: /app
    command: node --watch app.js

Instead of typing the huge docker run ... command in the terminal, you simply type docker-compose up and Docker handles creating the volumes, mapping the ports, and starting the server automatically!

Adding a Database (MariaDB/MySQL)

Databases in Docker are ephemeral by default—if the container stops or is removed, the data is lost. To persist data safely, we mount a volume from your local machine to the database’s internal data directory.

Here is how you run a MariaDB container using your local directory /opt/mysql/mydata/ for persistent storage:

docker run -d --name my-database -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret -v /opt/mysql/mydata/:/var/lib/mysql mariadb:latest
  • -d: Runs the container in the background (detached mode) so it doesn’t block your terminal.
  • -e MYSQL_ROOT_PASSWORD=...: Sets an environment variable required by the database image to set the root user password upon initialization.
  • -v /opt/mysql/mydata/:/var/lib/mysql: Crucial for persistence! This maps your local host folder to /var/lib/mysql. Your data will safely survive even if the container is destroyed.

Combining with Docker Compose

You can elegantly add the database to your docker-compose.yml file alongside your Node app:

version: '3.8'
services:
  web:
    image: node:latest
    container_name: express-app-3001
    ports:
      - "3001:3000"
    volumes:
      - .:/app
    working_dir: /app
    command: node --watch app.js
    depends_on:
      - db

  db:
    image: mariadb:latest
    container_name: my-database
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp
    ports:
      - "3306:3306"
    volumes:
      - /opt/mysql/mydata/:/var/lib/mysql

Now, running docker-compose up -d effortlessly spins up both your Express server and the MariaDB database at the same time!

Accessing the Database Console (-it)

Once your database container is running, you can log directly into the MySQL CLI prompt inside the container and run live SQL queries.

docker exec -it my-database mysql -u root -p

It will prompt you for the password. Once logged in, you can execute standard SQL commands:

SELECT NOW() FROM dual;

To exit, simply type exit; or press Ctrl + D.

Debugging with Docker Inspect

If you ever forget how a container was started, you can use the docker inspect command.

docker inspect my-database

This command outputs a massive JSON document containing everything Docker knows about that container.

Extracting Specific Information

Because the default JSON output is huge, you can use the --format flag to filter it down to exactly what you need. For example, to print out the environment variables (which is how we recovered your lost database password):

docker inspect my-database --format '{{range .Config.Env}}{{println .}}{{end}}'

This prints out a clean list of variables like MYSQL_ROOT_PASSWORD=root123.

Restarting Containers

1. Manual Restarts

To instantly stop and then start a container, use the docker restart command:

docker restart express-app-3001

If you are using Docker Compose, you can restart a specific service gracefully:

docker-compose restart web

2. Automatic Restart Policies

For production servers, you can configure Docker to restart containers automatically if they crash or the server reboots.

  • Via Command Line: Add the --restart=always flag. (docker run -d --restart=always ...)
  • Via Docker Compose: Simply add the property restart: always to your service block.

Viewing Container Logs

Basic Usage

To print out all the logs generated by a container since it started:

docker logs express-app-3001

Live Log Tailing (-f)

By adding the -f (follow) flag, Docker will stream new logs continuously to your terminal. Press Ctrl + C when you’re done watching.

docker logs -f express-app-3001

Viewing Recent Logs (--tail)

Use the --tail flag to only view the most recent output.

docker logs --tail 50 mysql-db

Bonus: Scaling (The “Wow” Factor)

Once your application gets popular, a single Node.js container might not be enough. With Docker Compose, scaling horizontally is incredibly simple:

docker-compose up -d --scale web=3

This instantly spins up 3 identical copies of your Node.js web server!

Note for Freshers: Don’t worry about orchestrators or auto-scaling just yet! Master volumes, port mapping, debugging, and basic Docker Compose first.

Intro to Docker Swarm

Docker Swarm is Docker’s native clustering and orchestration tool. It allows you to connect multiple machines (nodes) together into a single virtual cluster.

Starting a Swarm

To turn your single Docker machine into a Swarm Manager, run:

docker swarm init

Deploying a Stack with Scaling

To scale in Swarm mode, you add a deploy block directly into your docker-compose.yml file:

version: '3.8'
services:
  web:
    image: node:latest
    deploy:
      replicas: 5  # Runs exactly 5 copies of your app
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
      update_config:
        parallelism: 2  # Updates 2 containers at a time during rollouts

Once your file is updated, you deploy it as a Stack across the entire cluster:

docker stack deploy -c docker-compose.yml my_app_stack

Swarm reads the deploy block, automatically spins up your containers, and handles the load balancing across them!