7 Docker features you might not be aware of

Docker has revolutionized the way we develop, deploy, and manage applications. While most developers are familiar with its containerization capabilities, Docker offers a bunch of lesser-known features that can significantly enhance your development workflow. In this blog post, we'll explore 7 such unconventional ways to leverage Docker that you might not have considered before.
docker-ways

1. Docker as a Time Machine

One of the most fascinating aspects of Docker is its ability to act as a time machine for software environments. Since Docker Hub's release in 2014, we've had access to an ever-growing library of operating systems, programming languages, and software versions.
go-versions

Use Case: Testing backwards compatibility, Save your memory

Imagine you need to ensure your code compiles on the last three major versions of a programming language. Instead of managing multiple versions on your local machine, you can use Docker to create isolated environments for each version.
Here's an example using Go:

# Go 1.16
FROM golang:1.16
WORKDIR /app
COPY . .
RUN go build -v ./...

# Go 1.17
FROM golang:1.17
WORKDIR /app
COPY . .
RUN go build -v ./...

# Go 1.18
FROM golang:1.18
WORKDIR /app
COPY . .
RUN go build -v ./...

By using this multi-stage Dockerfile, you can easily test your code against multiple Go versions without cluttering your development environment. Most importantly, you can reduce the size of image from GBs to MBs. Learn more about Multi-stage builds here.

2. Running Legacy Code

As technology evolves, maintaining legacy code becomes increasingly challenging. Docker provides an elegant solution to this problem by allowing you to create consistent environments for running older software.

Use Case: Python 2 legacy Application

legacy-code
Let's say you have a critical data pipeline component written in Python 2 that hasn't been updated since 2020. Instead of rewriting the entire application, you can containerize it:

FROM ubuntu

# Install dependencies
RUN apt update -y && apt install -y wget tar gcc gcc-c++ make libopenssl-devel python-pip

# Download and unzip Python 2.4.3 source
RUN wget --no-check-certificate https://www.python.org/ftp/python/2.4.3/Python-2.4.3.tgz -O /opt/Python-2.4.3.tgz
RUN tar -xzvf /opt/Python-2.4.3.tgz --directory /opt/

# Compile and install Python 2.4
RUN cd /opt/Python-2.4.3/ && ./configure
RUN cd /opt/Python-2.4.3/ && make
RUN cd /opt/Python-2.4.3/ && make install

# Remove Downloads and project folder
RUN rm -rvf /opt/Python*

# Default entrypoint is Python 2.4 shell
CMD python

By using this Dockerfile, you can run the legacy Python 2 application in a Docker container, ensuring that it runs consistently across different environments.

docker run --name legacy_script -it --rm -v$(pwd):/opt/ -w /opt/ rohit_ghumare/python24
python my_project_main.py

This approach ensures that the legacy code runs in a consistent environment, regardless of the host system's Python version.

3. Local Development Stack with Docker Compose

Docker Compose is a powerful tool for defining and running multi-container Docker applications. It's particularly useful for setting up local development environments that mirror production setups.
full-stack

Use Case: Full-Stack Application Development

Consider a full-stack application with a React frontend, a Node.js backend, and a MongoDB database. Here's how you can set this up using Docker Compose:

version: '3.8'
services:
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://localhost:5000

  backend:
    build: ./backend
    ports:
      - "5000:5000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - MONGODB_URI=mongodb://db:27017/myapp

  db:
    image: mongo:4.4
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db

volumes:
  mongodb_data:

This setup allows developers to run the entire application stack locally with a single command: docker compose up.

4. Integration Testing with Testcontainers

Testcontainers is a powerful library that leverages Docker to provide lightweight, throwaway instances of common databases, web browsers, or anything else that can run in a Docker container.
testing

Use Case: Database Integration Testing

Here's an example of how you can use Testcontainers with Java and PostgreSQL:

import org.testcontainers.containers.PostgreSQLContainer;
import org.junit.jupiter.api.Test;

class DatabaseTest {
    @Test
    void testWithPostgres() {
        try (var postgres = new PostgreSQLContainer<>("postgres:13")) {
            postgres.start();
            
            String jdbcUrl = postgres.getJdbcUrl();
            String username = postgres.getUsername();
            String password = postgres.getPassword();
            
            // Use these credentials to connect to the database and run your tests
            // ...
        }
    }
}

This approach ensures that your tests always run against a clean, isolated database instance, improving test reliability and consistency.

5. Improving Application Security with Docker Scout

Docker Scout is a powerful tool for scanning container images and filesystems for vulnerabilities. It goes beyond simple vulnerability scanning by providing actionable insights and recommendations.

Use Case: Continuous Security Scanning

Integrate Docker Scout into your CI/CD pipeline to automatically scan images before deployment:

stages:
  - build
  - scan
  - deploy

build:
  stage: build
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .

scan:
  stage: scan
  script:
    - docker scout cves myapp:$CI_COMMIT_SHA
    - docker scout recommendations myapp:$CI_COMMIT_SHA

deploy:
  stage: deploy
  script:
    - docker push myapp:$CI_COMMIT_SHA
  only:
    - main

This setup ensures that your images are scanned for vulnerabilities and optimized before being deployed to production.

If you've Docker desktop on your system, you can use the Docker Desktop CLI to run the following command:

docker scout quickview

Quickview of my recently pushed docker image:
Screenshot 2024-09-03 at 11.39.17

6. Docker for Cross-Platform Development

Docker can be an invaluable tool for developers working on cross-platform applications, allowing them to test their code on different operating systems without the need for multiple physical or virtual machines.

Use Case: Testing a C++ Application on Multiple Platforms

test-c
Imagine you're developing a C++ application that needs to run on Ubuntu, CentOS, and Alpine Linux. You can use Docker to compile and test your application on all these platforms:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y build-essential
COPY . /app
WORKDIR /app
RUN g++ -o myapp main.cpp
CMD ["./myapp"]
FROM centos:8
RUN yum group install -y "Development Tools"
COPY . /app
WORKDIR /app
RUN g++ -o myapp main.cpp
CMD ["./myapp"]
FROM alpine:3.14
RUN apk add --no-cache g++
COPY . /app
WORKDIR /app
RUN g++ -o myapp main.cpp
CMD ["./myapp"]

By building and running these containers, you can quickly test your application's compatibility across different Linux distributions.

7. Docker for Reproducible Research

In scientific computing and data science, reproducibility is crucial. Docker can help create reproducible research environments, ensuring that analyses can be replicated exactly, regardless of the host system.
research

Use Case: Data Science Project Environment

Create a Dockerfile that encapsulates all the dependencies for a data science project:

FROM python:3.9

RUN pip install jupyter pandas numpy matplotlib scikit-learn

WORKDIR /project
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD ["jupyter", "notebook", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root"]

This setup ensures that all researchers working on the project have identical environments, reducing the "it works on my machine" problem and improving collaboration.

Conclusion

Docker's versatility extends far beyond simple containerization for deployment. By leveraging these unconventional use cases, you can significantly enhance your development workflow, improve code quality, and streamline your processes.

From using Docker as a time machine for backwards compatibility testing to leveraging it for reproducible research environments, the possibilities are vast. Tools like Docker Compose and Docker Scout further expand its capabilities, allowing for complex local development stacks and improved security practices.

Remember, the examples provided here are just the tip of the iceberg. Docker's flexibility means that it can be adapted to a wide range of use cases across various industries and development scenarios. As you become more comfortable with these advanced techniques, you'll find yourself reaching for Docker as a solution to an ever-growing list of development challenges.