My Account List Orders

Useful Docker Scripts

Table of Contents

  • Introduction
  • Chapter 1 Getting Started with Docker: Essential Commands.
  • Chapter 2 Building Your First Docker Image with a Dockerfile.
  • Chapter 3 Managing Multi-Container Applications with Docker Compose.
  • Chapter 4 Writing a Script to Automate Container Updates.
  • Chapter 5 Simplifying Common Tasks with Helper Scripts.
  • Chapter 6 Advanced Dockerfile Techniques for Optimized Builds.
  • Chapter 7 Automating Builds and Pushes with GitHub Actions.
  • Chapter 8 Scripting for Development Environments.
  • Chapter 9 Scripts for Continuous Integration and Testing.
  • Chapter 10 Creating Custom Entrypoint Scripts for Flexible Containers.
  • Chapter 11 Managing Docker Contexts in Bash Scripts.
  • Chapter 12 Scripting Network Creation and Management.
  • Chapter 13 Automating Volume Management and Data Persistence.
  • Chapter 14 A Script to Clean Up Unused Docker Resources.
  • Chapter 15 Scripting the Backup and Restore of Docker Volumes.
  • Chapter 16 Automating Docker Image Pruning.
  • Chapter 17 Scripting Health Checks for Your Containers.
  • Chapter 18 Building a Python Script to Interact with the Docker API.
  • Chapter 19 Creating a User Management Script for Shared Docker Environments.
  • Chapter 20 Scripting the Deployment of a Full Stack Application.
  • Chapter 21 A Script for Inspecting and Formatting Container Information.
  • Chapter 22 Automating the Tagging and Pushing of Images to a Registry.
  • Chapter 23 Scripting the Secure Handling of Secrets in Docker.
  • Chapter 24 A Script for Gracefully Stopping and Removing Containers.
  • Chapter 25 Advanced Scripting: Combining Docker Commands for Complex Workflows.

Introduction

There is a distinct moment in the journey of every Docker user that feels like a minor revelation. It often arrives after typing docker run with a dozen flags for the twentieth time in an hour, or after painstakingly executing a sequence of docker ps, docker stop, and docker rm commands to clean up a local environment. It's the moment you lean back in your chair, stare at the command prompt, and think, "There has to be a more efficient way to do this." You are, of course, absolutely correct. The path from repetitive manual commands to streamlined efficiency is paved with scripts.

Docker has fundamentally changed how we build, ship, and run software. It offers an incredible promise: a consistent, isolated, and portable environment for any application. By packaging an application and its dependencies into a single, standardized unit called a container, Docker solves the classic "it works on my machine" problem that has plagued developers for decades. This containerization approach provides a lightweight alternative to traditional virtual machines by virtualizing the operating system rather than the underlying hardware. This efficiency means you can run multiple containers on a single host, each with its own isolated set of processes and resources.

The power of Docker lies in its simplicity and its robust command-line interface (CLI). With a few commands, you can pull an image from a registry like Docker Hub, run it as a container, and have a complex application, like a database or a web server, up and running in seconds. This is a monumental leap forward from the days of manual installations and complex dependency management. However, relying solely on manually typed commands is like owning a sports car and only ever driving it in first gear. You are experiencing the core function, but missing out on the speed, power, and elegance it's truly capable of.

This is where scripting enters the picture. A script is, at its core, a set of instructions saved in a file, designed to be executed by a program. For Docker users, this usually means shell scripts (like Bash) or scripts written in a high-level language like Python. These scripts take the individual, atomic commands of the Docker CLI and weave them together into powerful, automated workflows. They transform multi-step, error-prone manual processes into a single, reliable command. They are the key to unlocking the full potential of your Docker environment.

Think about the routine tasks you perform daily. Starting a specific development environment with multiple linked containers. Backing up a critical database volume. Cleaning up unused images and containers to reclaim disk space. Updating a running application to a new image version without downtime. Each of these workflows involves a sequence of Docker commands, and each is a perfect candidate for automation through scripting. A well-written script ensures that these tasks are performed consistently every single time, eliminating the risk of human error, such as forgetting a command-line flag or typing a container name incorrectly.

This book is born from the practical need for that automation. While there are countless resources available to teach you the fundamentals of Docker—what an image is, how to write a Dockerfile, the purpose of a container—many stop there. They give you the building blocks but don't always show you how to assemble them into something greater. This book is different. It is not just another guide to Docker commands; it is a practical, hands-on compendium of useful scripts designed to solve real-world problems and make your life as a developer, system administrator, or DevOps engineer significantly easier.

We will move beyond the basic docker run and docker build commands to explore the art of scripting. This book is a collection of recipes, a toolkit of ready-to-use solutions for the common, and sometimes uncommon, challenges you face when working with Docker. Each chapter is dedicated to a specific task and provides a complete, working script that you can use immediately. More importantly, we will break down how each script works, explaining the commands, the logic, and the techniques used, so you can not only use the scripts but also understand, adapt, and extend them to fit your unique requirements.

So, who is this book for? It is for the developer who wants to create a consistent, one-command setup for their local development environment. It is for the system administrator who needs to manage Docker hosts and automate routine maintenance tasks like backups and cleanup. It is for the DevOps professional who is building robust continuous integration and continuous deployment (CI/CD) pipelines. In essence, this book is for anyone who has experienced that moment of "there has to be a better way" and is ready to embrace automation.

The only prerequisite is a foundational understanding of Docker. You should be familiar with core concepts like images, containers, volumes, and networks. You should have Docker installed and have some experience running basic commands. You do not, however, need to be a scripting guru. We will start with simple shell scripts and gradually introduce more advanced techniques and even delve into using the Docker API with Python, ensuring that the material is accessible regardless of your current scripting proficiency.

Throughout the chapters of this book, we will embark on a journey from simple to complex. We begin by ensuring a solid foundation, covering essential commands and the fundamentals of building images with a Dockerfile and managing multi-container setups with Docker Compose. These early chapters lay the groundwork, ensuring you have the necessary building blocks at your disposal before we start assembling them into automated workflows. They serve as a refresher and a baseline for the scripting work to come.

From there, we dive headfirst into the world of automation. You will learn how to write a script that can automatically pull a new version of an image and restart a container to complete an update, a cornerstone of maintaining running applications. We will explore the creation of generic helper scripts that can simplify a wide array of common tasks, making your command-line interactions with Docker more powerful and less verbose. These scripts are designed to be practical tools you can integrate into your workflow immediately.

A significant portion of our journey will be dedicated to the fine art of resource management. Docker environments, especially those used heavily for development and testing, can quickly become cluttered with unused images, volumes, and stopped containers, consuming valuable disk space. We will provide and dissect scripts specifically designed to clean up this clutter safely and efficiently. You will learn how to automate the pruning of old images and how to implement a script that intelligently removes only the resources that are no longer needed.

Data is the lifeblood of many applications, and managing it within a containerized world presents its own set of challenges. We will tackle this head-on with chapters on scripting the management of Docker volumes. You will learn how to write robust scripts to perform one of the most critical administrative tasks: backing up your important data from a volume and, just as importantly, restoring it. This provides a safety net and is an essential skill for anyone running stateful applications in Docker.

As your proficiency grows, we will venture into more advanced territory. We’ll look at sophisticated Dockerfile techniques that help you create smaller, more secure, and more efficient images. An optimized image builds faster, consumes less disk space, and has a smaller attack surface, all of which are critical in a production environment. We will show you how to structure your Dockerfiles to take full advantage of Docker's layer caching mechanism for faster builds.

Automation extends beyond your local machine, and modern software development is deeply intertwined with CI/CD platforms. A dedicated chapter will guide you through the process of using GitHub Actions to automate the building and pushing of your Docker images to a registry. This bridges the gap between your code repository and your deployment environment, creating a seamless pipeline from commit to container image.

We will also explore how scripts can be tailored specifically for development environments. This includes scripts that set up complex application stacks, manage different Docker contexts for switching between local and remote Docker daemons, and script the creation of custom networks to ensure your containers can communicate in the way your application requires. These scripts help enforce consistency across a team of developers, ensuring everyone is working in an identical environment.

The interaction with containers doesn't stop once they are running. We will investigate how to script health checks to monitor the status of your applications and how to create custom entrypoint scripts that make your containers more flexible and configurable at runtime. You will even learn how to bypass the CLI and interact with the Docker Engine directly using its API, opening up a whole new world of automation possibilities with languages like Python.

Security is a paramount concern in any environment, and Docker is no exception. A dedicated chapter will focus on scripting the secure handling of secrets, a critical aspect of deploying applications that need to connect to databases or other protected services. We will look at different strategies for providing sensitive information to your containers without hardcoding it into your images or scripts, which is a common but dangerous anti-pattern.

Finally, we will bring everything together by tackling complex, real-world workflows. We will walk through scripting the deployment of a full-stack application, from building the images to launching the interconnected services. We will also cover scripts for administrative tasks like managing users in a shared Docker environment, gracefully stopping and removing containers to ensure clean shutdowns, and creating custom tools for inspecting and formatting container information to get the exact output you need.

The philosophy behind the scripts in this book is one of practicality and adaptability. They are written to be as clear and straightforward as possible, with comments to explain the more complex parts. However, they are not intended to be immutable artifacts. You are encouraged to take them, modify them, break them, and rebuild them to perfectly suit your needs. The goal is not just to provide you with a library of scripts but to empower you with the knowledge to create your own.

To get the most out of this book, I encourage you to be an active reader. Don’t just read the text; fire up your terminal and run the scripts. Experiment with the commands and try to modify the code to see what happens. The best way to learn scripting is by doing it, and the best way to understand a tool is to use it. Set up a safe, local Docker environment where you are free to experiment without fear of breaking anything critical.

The journey from a manual, command-by-command Docker user to a master of automation is a rewarding one. It will save you time, reduce errors, and ultimately allow you to focus on the more interesting and creative aspects of your work. The ability to encapsulate a complex process into a single, executable script is a superpower in the world of software development and operations.

This book is your guide to developing that superpower. It’s time to move beyond the drudgery of repetitive typing and embrace the elegance of automation. Let's stop typing and start scripting.


CHAPTER ONE: Getting Started with Docker: Essential Commands.

Before one can compose a symphony, one must first learn to play the individual notes. In the world of Docker automation, our notes are the command-line interface (CLI) commands. These are the fundamental verbs that allow us to communicate our intentions to the Docker daemon, the engine that powers our containerized world. While the ultimate goal of this book is to weave these commands into elegant and powerful scripts, that journey must begin with a solid understanding of the commands themselves. This chapter is your toolbox, a guided tour of the essential tools you will reach for time and time again. Mastering these commands is the first and most critical step toward becoming proficient in Docker and, by extension, in its automation.

We will explore the complete lifecycle of a container, from finding the right software package on a public registry to running it, inspecting it, managing its state, and finally, cleaning it up. Each command is a building block. Understanding not just what a command does, but also its various options and the context in which it is most effective, is paramount. These are the tools you will use to build the sophisticated automated workflows in the chapters to come. Think of this as your foundational training, ensuring your vocabulary is strong before you begin writing poetry.

Our journey begins where most Docker workflows do: with an image. An image is a blueprint, a read-only template containing the application, its libraries, and its dependencies. These images are typically stored in a registry, which is a repository for storing and distributing them. The most common and public registry is Docker Hub. The first thing you'll often need to do is find the official image for the software you want to use. For this, the docker search command is a useful, if basic, starting point. It queries Docker Hub for images matching your search term.

For instance, if you wanted to find images related to the popular web server Nginx, you would run:

docker search nginx

The output provides a list of images with columns for the name, a brief description, the number of stars (a rough popularity metric), and whether the image is official. Official images are a curated set of repositories from Docker, providing a vetted and well-documented starting point for common software. It is almost always best practice to use the official image if one is available, as they are maintained with security and stability in mind.

Once you have identified the image you want to use, such as the official nginx image, you need to bring it from the registry to your local machine. This is the job of the docker pull command. This command downloads the image layers required to assemble the complete image on your system. The syntax is straightforward, requiring the name of the image and, optionally, a tag. A tag is used to version an image, such as 1.21 or latest. If no tag is specified, Docker defaults to pulling the one tagged as latest.

To pull the official Nginx image, you would execute:

docker pull nginx

This is equivalent to docker pull nginx:latest. To pull a specific version, you would specify the tag like so: docker pull nginx:1.21. Using specific version tags instead of latest is a crucial practice for creating reproducible environments and reliable scripts. The latest tag can be updated at any time by the image maintainer, meaning your script could behave differently from one day to the next. Pinning to a specific version ensures you are always working with the exact same software. After pulling, you can see all the images on your local machine with the docker images command.

With an image downloaded to your machine, you have the blueprint. The next logical step is to use that blueprint to construct something real: a container. A container is a runnable instance of an image. The command to create and start a container is docker run, arguably the most important and feature-rich command in the Docker CLI. Its basic form is simple, but its power lies in the multitude of flags that can be used to configure how the container runs.

The simplest possible execution of this command is with an image like hello-world, which is designed for this very purpose.

docker run hello-world

When you execute this, a sequence of events unfolds. Docker checks if the hello-world image is present on your local system. If not, it automatically pulls it from Docker Hub. It then creates a new container from that image. It runs the command embedded within the image, which in this case prints a friendly message to your terminal. Finally, once the command is finished, the container stops. This simple example demonstrates the core function of creating and running a container.

For most real-world applications, however, you will need more control. You'll want to run containers in the background, interact with them, expose them to your network, and give them persistent data. This is where the flags for docker run come into play. Let’s start by distinguishing between two primary modes of operation: interactive and detached.

An interactive container is one that you connect your terminal to, allowing you to run commands inside it directly. This is achieved with the -it flags. The -i flag keeps standard input (STDIN) open, and the -t flag allocates a pseudo-TTY, which essentially gives you a command prompt inside the container. This is perfect for exploring an image or performing a one-off task. For example, to run an Ubuntu container and get a bash shell inside it, you would use:

docker run -it ubuntu /bin/bash

Your terminal prompt would change, and you would now be executing commands inside the Ubuntu container. This is incredibly useful for debugging or development. When you type exit, the bash process terminates, and the container stops.

The alternative mode is detached mode, specified with the -d flag. This is the workhorse for running long-lived services like web servers, databases, or APIs. When you run a container in detached mode, Docker starts the container and then returns you to your host terminal prompt, leaving the container running in the background. The command prints the unique ID of the newly created container as confirmation. For example, to start an Nginx web server in the background:

docker run -d nginx

You now have a web server running, but how do you access it? By default, containers are isolated from the host's network. To expose a service running inside a container, you must map a port from the host machine to a port inside the container using the -p flag. The format is -p [host_port]:[container_port]. If we want to access our Nginx server on port 8080 of our local machine, we would map it to port 80 inside the container, which is the default port Nginx listens on.

docker run -d -p 8080:80 nginx

Now, if you open a web browser and navigate to http://localhost:8080, you will see the default Nginx welcome page, served from within your container. This port mapping is fundamental to making containerized applications useful.

When you run a container, Docker assigns it a random, often whimsically generated name, like vigilant_murdock or dreamy_bassi. While amusing, these names are not helpful for automation. If you want to reliably refer to a specific container in a script, you need to give it a predictable name. The --name flag allows you to do just this. A well-named container is the first step towards a manageable environment.

docker run -d -p 8080:80 --name my-web-server nginx

Now, instead of needing to find its random name or ID, you can simply refer to this container as my-web-server in subsequent commands, which is far more convenient and is essential for writing robust scripts.

Another critical concept is data persistence. The file system inside a container is ephemeral by default. This means that if you remove the container, any data created or modified inside it is lost forever. This is fine for stateless applications, but disastrous for anything that needs to maintain state, like a database. Docker solves this with volumes. A volume is a mechanism for persisting data generated by and used by Docker containers. We will dive deep into volume management scripts in Chapter 13, but the basic usage with docker run is essential to know now.

The -v flag is used to mount a volume into a container. You can either let Docker manage the volume (a named volume) or map a specific directory from your host machine into the container (a bind mount). For a MySQL database, you might want to persist the data directory:

docker run -d --name my-database -e MYSQL_ROOT_PASSWORD=mysecretpassword -v mysql-data:/var/lib/mysql mysql

In this command, we've also introduced the -e flag, which sets an environment variable inside the container. This is a very common way to pass configuration details, like passwords or settings, to the application running in the container. The -v mysql-data:/var/lib/mysql part tells Docker to create a named volume called mysql-data (if it doesn't already exist) and mount it at the /var/lib/mysql path inside the container. Now, even if you remove the my-database container, the mysql-data volume will remain, and you can attach it to a new container to resume your work.

Finally, for many temporary tasks, such as running a test suite or a build process, you create a container, it performs its action, and then it stops. By default, this stopped container still exists on your system. Over time, this can lead to a significant amount of clutter. The --rm flag tells Docker to automatically remove the container as soon as it exits. This is a fantastic housekeeping feature.

docker run --rm -it ubuntu echo "This container will disappear when I'm done."

Once the echo command completes, the container is immediately and automatically removed, leaving your system clean. Using --rm for short-lived, single-task containers is a best practice that prevents the accumulation of stopped containers.

Once you have containers running, particularly in detached mode, you need a way to see what's going on. The primary command for this is docker ps. By itself, this command lists all of the running containers on your system. The output is a tidy table showing several useful pieces of information: the container's ID, the image it was created from, the command it's running, when it was created, its current status, any port mappings, and its name.

This view is crucial for getting a quick overview of your active environment. However, it only tells part of the story. To see all containers, including those that have stopped or exited, you must add the -a flag.

docker ps -a

This command is your go-to for a complete inventory. You will see containers that exited successfully (Status Exited (0)) and those that failed (e.g., Exited (1)). This is often the first step in debugging why a container isn't running as expected.

For scripting purposes, the default tabular output of docker ps is not always ideal. It's designed for human eyes. When you need to pass a list of containers to another command, you often just want the IDs. The -q (quiet) flag modifies the output to show only the container IDs, one per line. For example, to get a list of all container IDs, running or stopped:

docker ps -a -q

This output can be directly used in other commands. For example, a common pattern to remove all stopped containers is to use command substitution: docker rm $(docker ps -a -f "status=exited" -q). Here, we've also introduced the -f (or --filter) flag, which allows you to narrow down the list based on certain criteria, in this case, the container's status. This combination of filtering and quiet output is a powerful technique that forms the basis of many cleanup scripts.

Simply knowing a container is running is often not enough. You need to see its output or interact with it. For detached containers, which don't have their output connected to your terminal, the docker logs command is essential. It fetches the logs (standard output and standard error) from a container.

docker logs my-web-server

This will display all the logs generated by the Nginx container since it started. For a continuously running service, you might want to watch the logs in real-time, similar to the tail -f command in Linux. The -f (follow) flag for docker logs does exactly that.

docker logs -f my-web-server

Your terminal will now stream the logs as they are generated, which is invaluable for live monitoring and debugging.

Sometimes you need to do more than just view logs; you need to run a command inside a container that is already running. For example, you might want to inspect a configuration file in your running Nginx container or check the contents of a database. This is the job of the docker exec command. It allows you to "execute" a command in a running container.

To get an interactive shell inside our running my-web-server container, you would use:

docker exec -it my-web-server /bin/bash

This looks very similar to the docker run -it command, but the crucial difference is that docker exec attaches to an existing, running container, whereas docker run creates a new one. Using exec is the standard way to perform administrative tasks on a running service without needing to stop and restart it.

The lifecycle of a container is not just about creation; it's also about control. You will frequently need to stop, start, and restart your containers. The commands for these actions are straightforward: docker stop, docker start, and docker restart. The docker stop command gracefully shuts down a container by first sending a SIGTERM signal, allowing the application to perform a clean shutdown. If it doesn't stop within a grace period (typically 10 seconds), Docker follows up with a SIGKILL signal to terminate it forcefully.

docker stop my-web-server

Once a container is stopped, it no longer shows up in the default docker ps output, but it still exists and can be seen with docker ps -a. To bring it back to life, you use docker start.

docker start my-web-server

The container will resume with its previous configuration, including its name and any attached volumes. The docker restart command is simply a convenient shortcut that performs a stop followed by a start, which is useful for applying configuration changes that require a service reboot.

A well-maintained Docker environment is a clean one. Over time, stopped containers and unused images can accumulate, consuming significant disk space. We've already seen the --rm flag for docker run, but you also need commands to manually clean up existing resources. The docker rm command is used to remove one or more stopped containers.

docker rm [container_id_or_name]

You cannot remove a running container unless you use the -f (force) flag, but the cleaner approach is always to stop it first and then remove it. You can also pass a list of container IDs to remove multiple containers at once, a technique that, as mentioned, is perfect for scripting.

Similarly, images can take up even more space than containers. The docker rmi command is used to remove one or more images. An image cannot be removed if it is currently being used by a container, even a stopped one. You must remove the dependent containers first before you can remove the image they are based on.

docker rmi nginx:1.21

These manual cleanup commands are effective, but they are also prime candidates for automation. Later chapters will show you how to build scripts that intelligently find and remove unused resources, saving you time and disk space.

Finally, there is one command that acts as a universal tool for deep inspection: docker inspect. This command can be used on containers, images, volumes, and networks. It returns a detailed JSON array containing a wealth of information about the specified Docker object. The output can be overwhelming at first, but it is an incredibly powerful resource for automation.

docker inspect my-web-server

This will print a large JSON object to the screen, detailing everything about the container: its state, its configuration, its network settings (including its internal IP address), its volume mounts, and much more.

The true power of docker inspect for scripting is unlocked with its --format flag. This flag allows you to provide a Go template to extract specific pieces of information from the JSON output. Instead of parsing a huge JSON file with a tool like jq, you can ask Docker to give you just the data point you need. For example, to get only the internal IP address of a container:

docker inspect --format='{{.NetworkSettings.IPAddress}}' my-web-server

This ability to precisely query the state and configuration of any Docker object is what makes docker inspect a cornerstone of advanced scripting. You can use it to fetch dynamic information, like a container's IP address, and feed it into other commands or configuration files, creating truly dynamic and intelligent automation.

These commands—pull, run, ps, logs, exec, stop, rm, rmi, and inspect—form the core vocabulary of Docker. They are the essential tools you will use every day. By understanding their functions and options, you have built the necessary foundation. You can now manage the entire lifecycle of a container manually. With this solid grasp of the individual building blocks, we are now ready to take the next step: defining our own blueprints for applications by learning how to construct a Dockerfile.


This is a sample preview. The complete book contains 27 sections.