Skip to main content

Pro Tips for Using Docker

00:06:13:19

Docker has revolutionized the way developers build, ship, and run applications. It simplifies application deployment by creating lightweight, reproducible environments, which can run anywhere, whether on a developer's laptop, a testing server, or in production.

In this article, we'll explore some advanced tips and best practices for using Docker in development. These tips aim to improve your Docker workflow, enhance performance, and help you make the most out of Docker's features.

Understanding Docker

Docker is a platform that uses OS-level virtualization to deliver software in packages called containers. Containers are lightweight and contain everything needed to run an application, including code, runtime, libraries, and system tools. This ensures that applications work the same, regardless of where they are deployed.

Key Components of Docker

  1. Docker Engine: The core of Docker, which is responsible for creating and running containers.
  2. Docker Images: Read-only templates used to create containers. They contain the application code, libraries, and dependencies.
  3. Docker Containers: Instances of Docker images that run your application. Containers are isolated from one another but can communicate through defined channels.
  4. Docker Hub: A cloud-based repository where you can find and share Docker images.

Installing Docker

Installing Docker is straightforward. Here's a quick guide to get Docker running on your system:

For macOS

  1. Download and install Docker Desktop from Docker's official website.

  2. Once installed, launch Docker Desktop and follow the setup instructions.

  3. Verify the installation by opening a terminal and running:

    bash
    docker --version
    

For Windows

  1. Download Docker Desktop for Windows from Docker's official website.

  2. Follow the installation instructions, which include enabling WSL 2 during the setup process, as Docker Desktop relies on WSL 2 as the default backend for Windows.

  3. Once installed, launch Docker Desktop, and it will guide you through the initial configuration.

  4. Verify your Docker installation by opening a PowerShell or Command Prompt and running

    bash
    docker --version
    

For Linux

  1. Update your package index:

    bash
    sudo apt-get update
    
  2. Install Docker using the official Docker repository:

    bash
    sudo apt-get install docker-ce docker-ce-cli containerd.io
    
  3. Start the Docker service and enable it to start on boot:

    bash
    sudo systemctl start docker
    sudo systemctl enable docker
    
    

Building Your First Docker Container

Now that Docker is installed, let’s build and run a simple Docker container using a basic Node.js application as an example.

Step 1: Create a Simple Node.js Application

Create a directory for your project and set up a basic Node.js server:

  1. Create a new directory and navigate into it:

    bash
    mkdir my-node-app
    cd my-node-app
    
  2. Initialize a new Node.js project:

    bash
    npm init -y
    
  3. Install the necessary dependencies (if any), and create a simple index.js file:

    ts
    // index.ts
    import express, { Request, Response } from 'express';
    
    const app = express();
    const port = 3000;
    
    app.get('/', (req: Request, res: Response) => {
      res.send('Hello, Docker!');
    });
    
    app.listen(port, () => {
      console.log(`Server running at http://localhost:${port}/`);
    });
    

Step 2: Create a Dockerfile

A Dockerfile is a script containing a series of instructions used to create a Docker image. Create a Dockerfile in your project directory with the following content:

dockerfile

# Use an official Node.js runtime as a base image
FROM node:20-alpine

# Set the working directory inside the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install application dependencies
RUN npm install

# Copy the application code into the container
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Define the command to run the app
CMD ["node", "index.js"]

Step 3: Build and Run Your Docker Image

  1. Build the Docker image from your Dockerfile:

    bash
    docker build -t my-node-app .
    

This command builds the image and tags it as my-node-app.

  1. Run the Docker container from the image:

    bash
    docker run -p 3000:3000 my-node-app
    

This command runs the container, mapping port 3000 on your machine to port 3000 inside the container.

  1. Open your browser and navigate to http://localhost:3000 to see your application running inside a Docker container.

Tips

1. Use Multi-Stage Builds

One of Docker's most powerful features is the ability to use multi-stage builds. This technique allows you to use multiple FROM statements in your Dockerfile, where you can build your application in one stage and then copy only the necessary files into a smaller, final image.

dockerfile
# First stage: Build the application
FROM node:18 as builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

# Second stage: Copy the build artifacts
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html

This approach drastically reduces the final image size, making your deployments faster and more efficient.

2. Optimize Dockerfile Caching

Docker uses a caching mechanism to speed up the build process. To take full advantage of this, you should order your Dockerfile instructions from the least frequently changed to the most frequently changed. For example, the COPY instruction should ideally be placed after installing dependencies to avoid re-installing them every time you make a small code change.

dockerfile

# Example of optimized Dockerfile
FROM node:18
WORKDIR /app

# Install dependencies (cached if package.json or package-lock.json hasn't changed)
COPY package*.json ./
RUN npm install

# Copy application code
COPY . .

# Build application
RUN npm run build

3. Keep Your Images Lightweight

Reducing the size of your Docker images can have a significant impact on the performance of your applications, especially in CI/CD pipelines. Use lightweight base images like alpine whenever possible, and remove unnecessary files and layers in your Dockerfile.

dockerfile

# Use a lightweight base image
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install && npm run build

# Remove unnecessary dependencies
RUN npm prune --production

4. Use .dockerignore to Exclude Files

Similar to .gitignore, the .dockerignore file tells Docker which files and directories to ignore during the build process. This can help speed up builds and reduce the size of the final image by excluding unnecessary files.

dockerfile
# .dockerignore
node_modules
dist
.git
Dockerfile
.dockerignore

5. Leverage Docker Compose for Multi-Container Applications

Docker Compose is a powerful tool that allows you to define and run multi-container Docker applications. Use it to set up your development environment with all necessary services (e.g., a database, cache, or message broker) in a single command.

yaml
version: '3.8'
services:
app:
build: .
ports:
   - '3000:3000'
depends_on:
   - database
   - kafka
environment:
   DATABASE_URL: postgres://user:password@database:5432/mydatabase
   KAFKA_BROKER: kafka:9092

database:
image: postgres:13
environment:
   POSTGRES_USER: user
   POSTGRES_PASSWORD: password
   POSTGRES_DB: mydatabase
volumes:
   - db-data:/var/lib/postgresql/data

kafka:
image: bitnami/kafka:latest
ports:
   - '9092:9092'
environment:
   KAFKA_BROKER_ID: 1
   KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
   KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9092,OUTSIDE://localhost:9092
   KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:9092
   KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
depends_on:
   - zookeeper

zookeeper:
image: bitnami/zookeeper:latest
ports:
   - '2181:2181'
environment:
   ALLOW_ANONYMOUS_LOGIN: 'yes'

microservice1:
build: ./microservice1
depends_on:
   - kafka
environment:
   KAFKA_BROKER: kafka:9092
ports:
   - '4000:4000'

microservice2:
build: ./microservice2
depends_on:
   - kafka
environment:
   KAFKA_BROKER: kafka:9092
ports:
   - '5000:5000'

volumes:
db-data:

Explanation:

  1. App Service: Connects to both the PostgreSQL database and Kafka broker using environment variables for the database URL and Kafka broker address.

  2. Database Service: Uses PostgreSQL with a persistent volume for data storage.

  3. Kafka Service: Runs an Apache Kafka broker, which relies on Zookeeper for coordination.

  4. Zookeeper Service: Supports Kafka by handling coordination tasks.

  5. Microservices (Microservice1 & Microservice2): Each microservice is built from its own Dockerfile (./microservice1 and ./microservice2 directories, respectively), connects to Kafka, and exposes its respective ports.