Terminus
Learning Docker (The Easy Way) Using LazyDocker & Warp

Learning Docker (The Easy Way) Using LazyDocker & Warp

[#setup-docker] Set up Docker [#setup-docker]

1. Download Docker.


You can find instructions here for installing it for your device, depending on if you’re on Mac, Linux, or Windows.
https://docs.docker.com/get-docker/

To check if you already have Docker installed, you can run this command:

 docker --version 
2. Open up the Docker application on your device.

[#setup-lazydocker-warp] Set up LazyDocker and Warp [#setup-lazydocker-warp]

These steps are optional, but I personally think that downloading these two tools is the easiest way to learn Docker. Here is why:

  • LazyDocker is a terminal-based UI for Docker that provides an interface for managing Docker containers, images, volumes, and networks. It consumes far less CPU and memory compared to Docker Desktop’s GUI, and feels much simpler and less overwhelming. On top of that, it’s ideal for managing Docker on remote servers, where a graphical interface is usually unavailable.
  • To download LazyDocker, run:
 brew install jesseduffield/lazydocker/lazydocker 
  • Warp is a terminal application that looks and works great out-of-the-box, which just makes it a really pleasant tool to learn with. On top of that, it has an AI integration that makes it a lot quicker to set up things like Docker images and run containers (way better than doing it manually). I’ll be using Warp’s AI feature a lot throughout the tutorial.
  • To install Warp, just run:  
 brew install --cask warp 

[#docker-building-blocks] Basic Explanation of Docker Building Blocks [#docker-building-blocks]

There are a few fundamental building blocks for understanding Docker.

  1. Containers: standalone packages that include everything needed to run an application - code, runtime, libraries, and dependencies. Containers are isolated from each other to help ensure consistency across different environments.
  2. Images: read-only templates used to create containers.
  1. Dockerfile: A text file containing a set of instructions on how to build a Docker image. It typically includes commands to set up the environment, install dependencies, copy files, and the command to run the application.

Essentially, Dockerfiles are blueprints used to create Docker images, and Docker images are the blueprints used to create Docker containers. The reason why they do this is…

How the flow usually works is:

  1. Build: You write a Dockerfile and use the [.inline-code]docker build[.inline-code] command to create an image.
  2. Run: You use the [.inline-code]docker run[.inline-code] command to create and start a container from the image.
  3. Manage: You can start, stop, and manage containers. For example, [.inline-code]docker ps[.inline-code] lists running containers, and [.inline-code]docker stop[.inline-code] stops a running container.

[#setup-dockerfile-image-container] Set up a Dockerfile, Docker Image, Docker Container [#setup-dockerfile-image-container]

Let’s apply these concepts. This is where LazyDocker and Warp will come in handy.

1. Open up your Warp terminal and press CMD-D to open up a split pane to the right. On the right pane, run the command:
 lazydocker 

Which will open up the LazyDocker TUI in that pane. Your setup should look something like this:

2. On the left, type in the following prompt into Warp:


“Can you create a Dockerfile for me and explain what it does?”

The response you get may differ depending on what Warp’s AI (Agent Mode) feature outputs, but here is the Dockerfile it created for me:

  
# Use the official Ubuntu base image
FROM ubuntu:20.04

# Set the maintainer label
LABEL maintainer="your_email@example.com"

# Update the package repository and install some software packages
RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip

# Set the working directory inside the container
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Make port 80 available to the world outside this container
EXPOSE 80

# Define the command to run the application
CMD ["python3", "app.py"]

Here is the breakdown of the Dockerfile:

  • [.inline-code]FROM ubuntu:20.04[.inline-code] This line specifies the base image to use for the Docker container, which in this case is Ubuntu 20.04.
  • [.inline-code]LABEL maintainer="your_email@example.com"[.inline-code] Sets a label to indicate the maintainer of the image.
  • [.inline-code]RUN apt-get update && apt-get install -y python3 python3-pip[.inline-code] Updates the package list and installs Python 3 and pip3 in the container.
  • [.inline-code]WORKDIR /app[.inline-code] Sets the working directory inside the container to /app.
  • [.inline-code]COPY . /app[.inline-code] Copies all the files from the current directory on the host machine into the /app directory in the container.
  • [.inline-code]EXPOSE 80[.inline-code] Opens port 80 so this container can be accessible over HTTP.
  • [.inline-code]CMD ["python3", "app.py"][.inline-code] Specifies the command to run the application, which in this case is "python3 app.py".
3. Next, I asked Warp’s Agent Mode:

“Please build the Dockerfile you just created”

I like typing my intent into Warp using natural language because I don’t have to worry about adding all the right flags/directory names correctly when running the base command. Even though I’ll eventually need to learn the meaning behind each parameter, I don’t want to get bogged down by this during my beginner learning stage. The command it generated for me was:

 docker build -t my-python-app 

Once you create the image, you should see it show up in LazyDocker’s TUI:

4. The next thing I will ask Warp’s Agent Mode is:


“Can you create a container from the Docker image you just created?”

And you can see in the screenshot below that it generates the command for me:

 docker run -d --name my-python-app-container my-python-app:v1 

Another thing I like about using Warp’s Agent Mode is that it can easily help me debug and fix errors without much context switching. When I tried to run this command at first, I ran into an error because my Docker image was trying to run the command [.inline-code]python3 app.py[.inline-code] but app.py did not exist:

As you can see in the screenshot above, Warp generated this command:

  
# Create a simple placeholder app.py file
echo 'print("Hello, Docker!")' > app.py

# Rebuild the Docker image
docker build -t my-python-app:v1 

# Run the container with the new image
docker run -d --name my-python-app-container my-python-app:v1

Which creates a simple [.inline-code]app.py[.inline-code] file in my directory that prints “Hello Docker!”, and then (my favorite part) regenerates the Docker image and Docker container without me having to prompt it to rebuild everything. After running these commands, you’ll see that my LazyDocker TUI now shows a running container:

5. The last thing you can do with containers is manage them. LazyDocker makes this really easy. Press “x” to see the menu, and do things like “pause” or “remove” a container.

[#why-docker-is-great] Why Docker is great for containerization & microservices [#why-docker-is-great]

Now that we understand the basic flow of how to use Docker, let’s dive into why the concept of Docker containers are important to developer productivity. To do this, we’re going to set up multiple services running in different containers, demonstrating how Docker can easily help you set up a simple microservices architecture with minimal hassle.

In fact, I’m going to let Warp walk you through this demo. I’m going to type the following prompt into Warp’s Agent Mode:


Can you create 3 different containers each running different types of services to demonstrate how Docker can easily create a microservices architecture?

First, it prompts you to run:

 docker network create microserves_demo 

Which creates a new user-defined network in Docker named “microservices_demo” and allows multiple Docker containers to communicate with each other. Then, it asks to create three different Docker containers running different types of services:

Here’s the specific command:

 
docker run -d --name web_service --network microservices_demo nginx && \
docker run -d --name db_service --network microservices_demo -e MYSQL_ROOT_PASSWORD=root mysql && \
docker run -d --name cache_service --network microservices_demo redis

In this case, I was able to create these containers without first creating a Dockerfile or Docker image because these commands use existing official Docker images from the Docker Hub, which is a cloud-based repository that holds Docker images. These images are pre-built and maintained by their respective communities.

Once I run this command, I can see that my three new services ([.inline-code]cache_service[.inline-code], [.inline-code]db_service[.inline-code], and [.inline-code]web_service[.inline-code]) are all running in their own Docker containers in LazyDocker. 

Here are two important takeaways from this demonstration.

  1. All three of these Docker containers ([.inline-code]cache_service[.inline-code], [.inline-code]db_service[.inline-code], and [.inline-code]web_service[.inline-code]) run in isolated environments.

    For example, suppose that the [.inline-code]web_service[.inline-code] container creates a file at [.inline-code]/app/logs/error.log[.inline-code]. This file is only visible within that container and does not affect the [.inline-code]db_service[.inline-code] container.

    Another example is that the [.inline-code]web_service[.inline-code] container can run processes like nginx which is independent of the MySQL processes running in the [.inline-code]db_service[.inline-code] container. I can even run a command like [.inline-code]ps aux[.inline-code] inside the [.inline-code]web_service[.inline-code] container and see processes only related to that container (and not [.inline-code]db_service[.inline-code]).

  2. These three services are able to communicate with each other over the microservices_demo network.

    For example, let’s say [.inline-code]web_service[.inline-code] needs to store user profile data in the database managed by [.inline-code]db_service[.inline-code]. Or maybe [.inline-code]web_service[.inline-code] wants to use [.inline-code]cache_service[.inline-code] to cache user session data or frequently accessed query results to avoid hitting the database repeatedly. Docker would automatically set up a DNS service within the network that maps the container names to their IP address. 

This only scratches the surface of what Docker can do, but with Warp and LazyDocker, there’s a lot of exploration you can do with the tool on your own to learn more. Good luck!