Understanding Dockerfile Instructions and Layers
Let's start by creating a Dockerfile that utilizes various instructions. We'll build an image for a Python web application using Flask, and along the way, we'll explore how each instruction contributes to the layers of our Docker image.
- First, let's create a new directory for our project. In the WebIDE terminal, run:
mkdir -p ~/project/advanced-dockerfile && cd ~/project/advanced-dockerfile
This command creates a new directory called advanced-dockerfile
inside the project
folder and then changes into that directory.
-
Now, let's create our application file. In the WebIDE file explorer (usually on the left side of the screen), right-click on the advanced-dockerfile
folder and select "New File". Name this file app.py
.
-
Open app.py
and add the following Python code:
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f"Hello from {os.environ.get('ENVIRONMENT', 'unknown')} environment!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
This is a simple Flask application that responds with a greeting message, including the environment it's running in.
- Next, we need to create a
requirements.txt
file to specify our Python dependencies. Create a new file named requirements.txt
in the same directory and add the following content:
Flask==2.0.1
Werkzeug==2.0.1
Here, we're specifying exact versions for both Flask and Werkzeug to ensure compatibility.
- Now, let's create our Dockerfile. Create a new file named
Dockerfile
(with a capital 'D') in the same directory and add the following content:
## Use an official Python runtime as the base image
FROM python:3.9-slim
## Set the working directory in the container
WORKDIR /app
## Set an environment variable
ENV ENVIRONMENT=production
## Copy the requirements file into the container
COPY requirements.txt .
## Install the required packages
RUN pip install --no-cache-dir -r requirements.txt
## Copy the application code into the container
COPY app.py .
## Specify the command to run when the container starts
CMD ["python", "app.py"]
## Expose the port the app runs on
EXPOSE 5000
## Add labels for metadata
LABEL maintainer="Your Name <[email protected]>"
LABEL version="1.0"
LABEL description="Flask app demo for advanced Dockerfile techniques"
Now, let's break down these instructions and understand how they contribute to the layers of our Docker image:
FROM python:3.9-slim
: This is always the first instruction. It specifies the base image we're building from. This creates the first layer of our image, which includes the Python runtime.
WORKDIR /app
: This sets the working directory for subsequent instructions. It doesn't create a new layer, but affects how following instructions behave.
ENV ENVIRONMENT=production
: This sets an environment variable. Environment variables don't create new layers, but they are stored in the image metadata.
COPY requirements.txt .
: This copies the requirements file from our host into the image. This creates a new layer containing just this file.
RUN pip install --no-cache-dir -r requirements.txt
: This runs a command in the container during the build process. It installs our Python dependencies. This creates a new layer that contains all the installed packages.
COPY app.py .
: This copies our application code into the image, creating another layer.
CMD ["python", "app.py"]
: This specifies the command to run when the container starts. It doesn't create a layer, but sets the default command for the container.
EXPOSE 5000
: This is actually just a form of documentation. It tells Docker that the container will listen on this port at runtime, but doesn't actually publish the port. It doesn't create a layer.
LABEL ...
: These add metadata to the image. Like ENV instructions, they don't create new layers but are stored in the image metadata.
Each RUN
, COPY
, and ADD
instruction in a Dockerfile creates a new layer. Layers are a fundamental concept in Docker that allow for efficient storage and transfer of images. When you make changes to your Dockerfile and rebuild the image, Docker will reuse cached layers that haven't changed, speeding up the build process.
- Now that we understand what our Dockerfile is doing, let's build the Docker image. In the terminal, run:
docker build -t advanced-flask-app .
This command builds a new Docker image with the tag advanced-flask-app
. The .
at the end tells Docker to look for the Dockerfile in the current directory.
You'll see output showing each step of the build process. Notice how each step corresponds to an instruction in our Dockerfile, and how Docker mentions "Using cache" for steps that haven't changed if you run the build command multiple times.
- Once the build is complete, we can run a container based on our new image:
docker run -d -p 5000:5000 --name flask-container advanced-flask-app
This command does the following:
-d
runs the container in detached mode (in the background)
-p 5000:5000
maps port 5000 on your host to port 5000 in the container
--name flask-container
gives a name to our new container
advanced-flask-app
is the image we're using to create the container
You can verify that the container is running by checking the list of running containers:
docker ps
- To test if our application is running correctly, we can use the
curl
command:
curl http://localhost:5000
You should see the message "Hello from production environment!"
If you're having trouble with curl
, you can also open a new browser tab and visit http://localhost:5000
. You should see the same message.
If you encounter any issues, you can check the container logs using:
docker logs flask-container
This will show you any error messages or output from your Flask application.