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.
Table of Contents
- Common Docker Commands
- Using Docker to Run Applications from Existing Images
- Running From Existing Image Example 1 - Jenkins Container
- Running From Existing Image Example 2 - MySQL Container
- Running From Existing Image Example 3 - Flask Container
- Using Docker Volumes
- Using Bind Mounts Example - Jenkins
- Using Bind Mounts with MySQL
- Using Bind Mounts with Flask To-Do App
- Building Your Own Docker Images
- Pushing Images to Docker Hub
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 viahttp://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 viahttp://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, andlatest
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 runningmysql-container
. It then runs the MySQL client, logging in as the root user (-uroot
) and prompts for the password. Entermy-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 like0.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 tohttp://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
todevelopment
, 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
toWorld
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
toOpenAI
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.