Docker Cheat Sheet and Tutorial

Welcome to the Docker Cheatsheet and Tutorial! This page will guide you through using Docker for your development needs, covering both existing Docker images and creating your own custom images. Whether you are a beginner or looking to enhance your Docker skills, this guide provides valuable insights and commands to get you started.

 

Common Docker Commands

  • docker run: Runs a container from an image.
  • docker pull: Pulls an image from a Docker registry.
  • docker build: Builds an image from a Dockerfile.
  • docker ps: Lists running containers.
  • docker stop: Stops a running container.
  • docker rm: Removes a stopped container.
  • docker images: Lists downloaded images.
  • docker rmi: Removes an image.
  • docker exec: Runs a command inside a running container.

Using Docker to Run Applications from Existing Images

In this section, we will explore how to use Docker to run various applications using existing Docker images. Running applications in Docker containers allows for consistent environments, isolation, and easy deployment. We will cover several examples, including running a Jenkins CI/CD server, a MySQL database, and a custom Flask To-Do application. Each example will highlight different Docker concepts, such as port mapping, environment variables, and data persistence, to provide a comprehensive understanding of containerized application management.

Running From Existing Image Example 1 - Jenkins Container

Jenkins is a powerful tool used for automating software development processes. Running Jenkins in Docker allows for isolated and consistent CI/CD environments. We’ll demonstrate two methods to run Jenkins: mapping specific ports and using Docker's automatic port mapping feature.

Method 1: Running Jenkins with Specific Ports

Using the -p flag allows you to specify exactly which ports on the host will be mapped to the container. This is useful for ensuring consistency in accessing services.

  • Step 1: Pull the Jenkins image from Docker Hub. If you don’t pull it manually, Docker will automatically pull the image when you run the container.
  • Command:
    docker pull jenkins/jenkins:lts
  • Step 2: Run the Jenkins container with specific ports. Here, -p 8080:8080 maps port 8080 on your host to port 8080 in the container, making Jenkins accessible via http://localhost:8080. The -p 50000:50000 maps the port for Jenkins agents.
  • Command:
    docker run --name jenkins-container -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts

Method 2: Using Automatic Port Mapping

Docker's -P flag lets Docker map any available host port to the container's exposed ports. This is useful when you don't care about which specific port is used, as long as the service is accessible. Docker will randomly assign a free port.

  • Command:
    docker run --name jenkins-container -P jenkins/jenkins:lts
  • After running this command, use docker ps to check which ports have been assigned and access Jenkins via http://localhost:[random_port].

Naming Containers

In the commands above, we use the --name option to give our container a specific name, jenkins-container. Naming containers helps identify and manage them easily, especially when running multiple instances. If you don’t name the container, Docker will generate a random name, which can make it harder to manage your environment.

 

Running From Existing Image Example 2 - MySQL Container

MySQL is a popular relational database management system. Running MySQL in Docker allows you to set up a consistent and isolated database environment. We'll cover how to run a MySQL container with proper configuration using Docker concepts like ports, container naming, and images. Additionally, we'll show how to connect to the MySQL server from a MySQL Workbench client and directly from within the container.

Running MySQL with Environment Variables and Port Mapping

To run a MySQL container, we'll use environment variables to set configuration options and map the container's internal port to the host's port. This setup ensures that the MySQL server is accessible from outside the container.

  • Command:
    docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:latest
  • Explanation:
    Option Description
    --name mysql-container Assigns the name mysql-container to the running container, making it easier to manage. If you don't provide a name, Docker will generate a random one.
    -e MYSQL_ROOT_PASSWORD=my-secret-pw Sets the root password for the MySQL server using an environment variable. This is necessary for authentication.
    -p 3306:3306 Maps port 3306 on the host to port 3306 on the container. Ensure that port 3306 is available on the host machine, or choose a different port to avoid conflicts.
    -d Runs the container in detached mode, meaning it runs in the background.
    mysql:latest Specifies the image to use. mysql is the official MySQL image, and latest refers to the latest version of that image. Docker will automatically pull the image if it's not already available locally.

Connecting to MySQL Server from MySQL Workbench

After running the MySQL container, you can connect to it using MySQL Workbench. Here's how to set up the connection:

  • Step 1: Open MySQL Workbench and create a new connection.
  • Step 2: Set the connection name to anything you prefer.
  • Step 3: In the "Hostname" field, enter localhost. This is because the MySQL server is accessible on the host machine's localhost via the mapped port.
  • Step 4: In the "Port" field, enter 3306. This matches the port you mapped from the container to the host.
  • Step 5: Set the "Username" to root.
  • Step 6: Set the "Password" to my-secret-pw (the password you set using the environment variable).
  • Step 7: Click "Test Connection" to verify that you can connect to the MySQL server. If successful, save the connection settings.

Connecting to MySQL from Within the Container Using docker exec

You can connect to the MySQL server directly from within the container using the docker exec command. This method is useful for troubleshooting or running commands directly on the MySQL server.

  • Command:
    docker exec -it mysql-container mysql -uroot -p
  • This command uses docker exec to open an interactive terminal (-it) in the running mysql-container. It then runs the MySQL client, logging in as the root user (-uroot) and prompts for the password. Enter my-secret-pw when prompted.

Verifying the MySQL Connection

Once connected to the MySQL server (either from MySQL Workbench or from within the container), you can run a simple SQL command to verify that the connection is working:

  • SQL Command:
    SHOW DATABASES;
  • This command will list all the databases on the MySQL server, confirming that the connection has been successfully established.

Running From Existing Image Example 3 - Flask To-Do App Container

This example uses a custom Flask To-Do application, which is packaged as a Docker image and published on Docker Hub under the name supersqa/todo-app-flask-docker-demo. Running this app in Docker provides a consistent environment for testing and development. We'll demonstrate how to run the container using various Docker options, emphasizing Docker-specific concepts.

Method 1: Running the To-Do App with Automatic Port Mapping

The simplest way to run the Flask To-Do app container is by using the -P flag, which automatically maps the container's internal port to a random available port on the host. This method will pull the image from Docker Hub if it's not available locally and start the container.

  • Command:
    docker run --name todo-app -P -d supersqa/todo-app-flask-docker-demo
  • Explanation:
    Option Description
    --name todo-app Assigns the name todo-app to the running container, making it easier to manage and identify.
    -P Automatically maps any available host port to the container's internal port (5151). Docker chooses a random available port on the host and maps it to the container's port, ensuring the application is accessible.
    -d Runs the container in detached mode, meaning it runs in the background.
    supersqa/todo-app-flask-docker-demo Specifies the image to use. This is the Flask To-Do app image hosted on Docker Hub. Docker will automatically pull the image if it's not already available locally.
  • Accessing the App: After running the container, use the following command to find the mapped port:
    docker port todo-app
    Docker will display a message like 0.0.0.0:32768->5151/tcp, indicating the host port (32768 in this example) mapped to the container's port 5151. You can access the app by navigating to http://localhost:32768 in your web browser.

Method 2: Running the To-Do App with Specific Port Mapping

To ensure the Flask To-Do app is always accessible on a known port, you can specify the host port using the -p flag. This setup is essential for interacting with the application via the network.

  • Command:
    docker run --name todo-app -p 5151:5151 -d supersqa/todo-app-flask-docker-demo
  • Explanation:
    Option Description
    -p 5151:5151 Maps port 5151 on the host to port 5151 on the container. This ensures the app is accessible via http://localhost:5151. Ensure port 5151 is available on the host; otherwise, choose a different port to avoid conflicts.

Method 3: Running the To-Do App with Environment Variables and Volume

For more advanced configurations, you can use environment variables and volumes. Environment variables can configure aspects of the app (e.g., debug mode), and volumes can persist data outside the container.

  • Command:
    docker run --name todo-app -p 5151:5151 -e FLASK_ENV=development -v $(pwd)/app-data:/app/data -d supersqa/todo-app-flask-docker-demo
  • Explanation:
    Option Description
    -e FLASK_ENV=development Sets an environment variable FLASK_ENV to development, enabling debug mode for the Flask application.
    -v $(pwd)/app-data:/app/data Mounts a volume, mapping the host directory app-data to the container's /app/data directory. This is useful for persisting data generated by the app.

Interacting with the To-Do App

After running the container with port mapping, you can access the Flask To-Do app by navigating to http://localhost:5151 (or the randomly assigned port if using -P) in your web browser. The application will be running, and you can interact with it as if it were hosted on a traditional server.

  • To stop the container, use the following command:
    docker stop todo-app
  • To remove the container after stopping it, use:
    docker rm todo-app

Using Docker Volumes

Docker volumes are crucial for persisting data used by and generated in containers. Unlike files stored in the container’s writable layer, volumes are stored on the host file system, making them persistent across container restarts and even removals. Docker provides three types of volumes:

  • 1. Anonymous Volumes: Created without a specific name, these volumes are useful for temporary storage needs.
  • 2. Named Volumes: These volumes are explicitly named, making them easy to reference and share between multiple containers, especially when persistent data is needed.
  • 3. Bind Mounts: Bind mounts allow you to mount specific directories or files from the host file system into the container. This offers more control over what is mounted and is commonly used for development environments.

In this section, we will focus on the third type: Bind Mounts. Bind mounts offer a powerful way to map specific directories or files from the host into a container, providing immediate reflection of changes from the host in the container. This is particularly useful for development environments or when containers need direct access to host files.

Using Bind Mounts with Jenkins

This example demonstrates using a Jenkins container with a bind mount to persist configuration and job data on the host system, ensuring it remains accessible across container restarts.

Running Jenkins with Bind Mount

By using bind mounts, you can store Jenkins data on the host, making it easy to back up and modify outside of the container environment.

  • Command:
    docker run --name jenkins-container -v $(pwd)/jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 -d jenkins/jenkins:lts
  • Explanation:
    Option Description
    -v $(pwd)/jenkins_home:/var/jenkins_home Mounts the jenkins_home directory from the current directory on the host into the container at /var/jenkins_home, ensuring that all Jenkins configuration and job data is stored on the host.
    -p 8080:8080 Maps port 8080 on the host to port 8080 on the container, making Jenkins accessible via http://localhost:8080.
    -p 50000:50000 Maps port 50000 for Jenkins agents, allowing remote builds.
    -d Runs the container in detached mode.

Using Bind Mounts with MySQL

Bind mounts are useful for persisting MySQL database data on the host, ensuring it remains available across container lifecycles.

Running MySQL with Bind Mount

Bind mounts allow MySQL to store its data directly on the host, providing persistence and ease of access for backup and recovery.

  • Command:
    docker run --name mysql-container -v $(pwd)/mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:latest
  • Explanation:
    Option Description
    -v $(pwd)/mysql_data:/var/lib/mysql Mounts the mysql_data directory from the current directory on the host into the container at /var/lib/mysql, ensuring that all MySQL data is stored on the host.
    -e MYSQL_ROOT_PASSWORD=my-secret-pw Sets the root password for the MySQL server.
    -p 3306:3306 Maps port 3306 on the host to port 3306 on the container, making the MySQL server accessible.
    -d Runs the container in detached mode.

Using Bind Mounts with Flask To-Do App

Bind mounts enable the Flask To-Do app to store its task data on the host, making development easier and ensuring data persistence.

Running Flask To-Do App with Bind Mount

Bind mounts allow the Flask To-Do app to save task data directly on the host, which is beneficial for development and data persistence.

  • Command:
    docker run --name todo-app-container -v $(pwd)/app-tasks:/app/tasks -p 5151:5151 -d supersqa/todo-app-flask-docker-demo
  • Explanation:
    Option Description
    -v $(pwd)/app-tasks:/app/tasks Mounts the app-tasks directory from the current directory on the host into the container at /app/tasks, ensuring that all task data is stored on the host and immediately reflects any changes.
    -p 5151:5151 Maps port 5151 on the host to port 5151 on the container, making the Flask To-Do app accessible via http://localhost:5151.
    -d Runs the container in detached mode.

By using bind mounts, these containers can easily manage data persistence, access host system files directly, and enable seamless development workflows. This approach is particularly beneficial when working with configuration files, databases, or application data that require consistent availability and accessibility.

Building Your Own Docker Images

Building custom Docker images is a powerful way to define your application's environment, dependencies, and configurations. By creating your own images, you ensure that your applications run consistently across different environments. This section will guide you through the process of building Docker images using Dockerfiles, best practices, and practical examples.

Introduction to Docker Images and Dockerfile

Docker images are the foundation of containers, providing the necessary OS, libraries, and application code. A Dockerfile is a text document that contains instructions for Docker to build an image. It allows you to automate the process of creating Docker images, ensuring consistency and repeatability.

Creating a Simple Dockerfile

We will start by creating a basic Dockerfile for a simple Python Flask application. This example will demonstrate how to set up a custom environment using Dockerfile instructions.

  • Step 1: Write the Dockerfile
    # Use an official Python runtime as a parent image
    FROM python:3.9-slim
    
    # Set the working directory in the container
    WORKDIR /app
    
    # Copy the current directory contents into the container at /app
    COPY . /app
    
    # Install any needed packages specified in requirements.txt
    RUN pip install --no-cache-dir -r requirements.txt
    
    # Make port 5000 available to the world outside this container
    EXPOSE 5000
    
    # Define environment variable
    ENV NAME World
    
    # Run app.py when the container launches
    CMD ["python", "app.py"]
                
  • Explanation:
    Instruction Description
    FROM python:3.9-slim Specifies the base image to use for the custom image. In this case, it's a slim version of Python 3.9.
    WORKDIR /app Sets the working directory in the container to /app.
    COPY . /app Copies the current directory contents into the container at /app.
    RUN pip install --no-cache-dir -r requirements.txt Installs the Python packages listed in requirements.txt using pip.
    EXPOSE 5000 Makes port 5000 available to the host system, allowing external access to the app.
    ENV NAME World Sets an environment variable NAME to World in the container.
    CMD ["python", "app.py"] Specifies the command to run when the container starts. In this case, it runs the Flask application.

Building the Docker Image

Once the Dockerfile is written, you can build the Docker image using the following command. This command should be run in the same directory as the Dockerfile.

  • Command:
    docker build -t my-flask-app .
  • Explanation:
    Option Description
    -t my-flask-app Tags the built image with the name my-flask-app, making it easier to reference.
    . Specifies the build context, which includes the current directory and its contents. The Dockerfile must be located here.

Best Practices for Writing Dockerfiles

Writing efficient and secure Dockerfiles is key to creating optimal Docker images. Here are some best practices to follow:

  • Keep Dockerfiles simple and readable: Write clear and concise Dockerfile instructions to make maintenance easier.
  • Use official base images: Start with a minimal and trusted base image to reduce vulnerabilities and image size.
  • Minimize the number of layers: Combine instructions where possible to reduce the image size and improve build performance.
  • Avoid storing sensitive data: Never hardcode secrets or passwords in the Dockerfile. Use environment variables or secret management tools.
  • Use multi-stage builds: If you have a complex build process, consider using multi-stage builds to keep the final image size minimal.

Using Environment Variables

Environment variables are a flexible way to configure your Docker containers. They can be defined in the Dockerfile or passed at runtime.

  • Example Command:
    docker run -e NAME=OpenAI my-flask-app
  • Explanation:
    Option Description
    -e NAME=OpenAI Sets the environment variable NAME to OpenAI inside the container, which can be accessed by the application.

Building Images with External Dependencies

If your application requires external dependencies, you can include them in your Dockerfile. This example shows how to install Python packages using pip.

  • Dockerfile Snippet:
    RUN pip install --no-cache-dir requests
            
  • Explanation:
    Instruction Description
    RUN pip install --no-cache-dir requests Installs the requests library using pip. The --no-cache-dir option ensures that no unnecessary cache files are stored in the image, keeping it clean.

Pushing Images to Docker Hub

After building your custom Docker image, you can push it to Docker Hub for sharing or deployment. This makes it easy to distribute and run your application on different hosts.

  • Command:
    docker push my-flask-app
  • Explanation:
    Option Description
    docker push my-flask-app Pushes the Docker image named my-flask-app to Docker Hub. Ensure you are logged in to your Docker Hub account before running this command.