DEV Community

Cover image for Creating a Docker Image for Beginners: A Complete Guide
Ritesh Kokam
Ritesh Kokam

Posted on

Creating a Docker Image for Beginners: A Complete Guide

After learning the Docker basics, it is time to create our images. Creating our own custom Docker image allows us to have more control over application configurations, dependencies, security, and more. And it is easy and fun. Get ready for it!


Table of Contents


Why Create Custom Docker Images?

Custom Docker images give us control over application configurations and dependencies, improving security and performance. We can create a custom Docker image tailored to our own needs, with the dependencies we want, and the code we want.

For example, we want everybody in our dev team to use Ubuntu 24.04 with Apache and the code in one GitHub repo. We could create a custom Docker image, upload it, and tell everybody in the dev team to work using that image.

No more dependencies problem, no more "I don't know how to install X" and, especially, no more "But it works on my computer".

We just need to create one image packaged with all we need, that executes every step we want.


Doing the process steps manually

One tip before creating our own custom Docker image is to do the process manually: We get a clean Linux system (a Virtual Machine created with Vagrant is an easy way as you can use the image and discard it later) and we run the commands we want.

Using a process of trial and error, we install and configure everything we want until it is done. We will repeat these steps on our Docker image.

For this, of course, you need to have Docker installed (and, if not, here's how to Install Docker).

Let's start then!

We want to run a Flask web application inside an Ubuntu OS. The steps we want to do are:

  • Install Ubuntu
  • Install Python and its required tools
  • Create a Python virtual environment
  • Install Flask dependency
  • Copy our Python code
  • Run the Flask web application

So, let's start!

First, let's download a Docker image, then create a container using that image, and run the bash terminal:

docker run -it ubuntu:24.04 bash
Enter fullscreen mode Exit fullscreen mode

Now we are in the container's terminal as root. Let's install our dependencies:

apt update
Enter fullscreen mode Exit fullscreen mode
apt install -y python3 python3-pip python3-venv
Enter fullscreen mode Exit fullscreen mode

Ubuntu 24.04 uses an externally managed Python environment, so instead of installing Flask directly into the system Python, we create a virtual environment:

python3 -m venv /opt/venv
Enter fullscreen mode Exit fullscreen mode

Activate it:

. /opt/venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Now install Flask:

pip install flask
Enter fullscreen mode Exit fullscreen mode

Now we have everything we need to run our code.

You can create a Flask app, search for one on GitHub that you want to use, or just use an application I have created for this tutorial.

Copy the code and paste it inside /opt/app.py.

import os
from flask import Flask

app = Flask(__name__)

@app.route("/")
def main():
    return "Welcome!"

@app.route("/How are you")
def how_are_you():
    return "I'm fine, how about you?"

@app.route("/about")
def about():
    return "I'm just a small Flask app :)"

@app.route("/info")
def info():
    return "I have been created just to pass Pau's subject."

if __name__ == "__main__":
    app.run()
Enter fullscreen mode Exit fullscreen mode

Everything is set, time to run the server!

/opt/venv/bin/flask --app /opt/app.py run --host=0.0.0.0 --port=5000
Enter fullscreen mode Exit fullscreen mode

Now the Flask server is listening on port 5000 inside the container.


Creating our Docker image

We have our server running perfectly. Now, we want to reproduce the same steps in a Docker image so we can distribute it.

First, close everything and check the container isn't running:

docker ps
Enter fullscreen mode Exit fullscreen mode

Now, let's create a folder for us to work in. Inside, create a Dockerfile (a text document that contains all the commands we want to run) where we will create a script to do all the steps we did before:

mkdir app_flask
cd app_flask

vim Dockerfile
Enter fullscreen mode Exit fullscreen mode

Our Dockerfile will contain:

FROM ubuntu:24.04

RUN apt-get update && \
    apt-get install -y python3 python3-venv && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /opt

COPY app.py /opt/app.py

RUN python3 -m venv /opt/venv && \
    /opt/venv/bin/pip install --no-cache-dir flask

EXPOSE 5000

CMD ["/opt/venv/bin/flask", "--app", "/opt/app.py", "run", "--host=0.0.0.0", "--port=5000"]
Enter fullscreen mode Exit fullscreen mode

The important difference from our original Dockerfile is that we use a Python virtual environment. This is appropriate for current Ubuntu releases, where Python packages should not be installed directly into the system-managed Python environment.

We also use the exec form of CMD. Docker supports both shell and exec forms, with the exec form providing clearer process and signal handling. EXPOSE documents that the application listens on port 5000; it does not publish the port to the host by itself.

Now we can run Docker build to run the script and create a Docker image. But before, as we see in the COPY line, we need to have our script in the same folder. Copy it or just download it with wget:

wget https://raw.githubusercontent.com/david1707/flask-app/main/app.py
Enter fullscreen mode Exit fullscreen mode

Now build the image:

docker build . -t david1707/app-flask-caminas:1.0
Enter fullscreen mode Exit fullscreen mode

Remember to change the image's name, following the convention of <YOUR_NAME>/<IMAGE_NAME>.

I have also added the 1.0 tag so that we can identify this version of the image. If no tag is specified, Docker uses latest by default, but explicit version tags are useful when you want to distinguish different image versions.

After the build completes, you have your custom Docker image.

Now let's run it:

docker run -d \
  --name app-flask-caminas \
  -p 5000:5000 \
  david1707/app-flask-caminas:1.0
Enter fullscreen mode Exit fullscreen mode

Now we can visit:

http://localhost:5000
Enter fullscreen mode Exit fullscreen mode

The Flask application is running inside our Docker container.


Making public our Docker image

Now we can distribute the Docker image and everybody will have the same image, with the same dependencies.

We can publish it in a Docker repository, for example, in Docker Hub. Very much like GitHub, Docker Hub allows us to store and share container images.

You have to register in Docker Hub first, then log in:

docker login
Enter fullscreen mode Exit fullscreen mode

If your image isn't already tagged with your Docker Hub username, tag it:

docker tag david1707/app-flask-caminas:1.0 <YOUR_DOCKER_USERNAME>/app-flask-caminas:1.0
Enter fullscreen mode Exit fullscreen mode

Then push it to Docker Hub:

docker push <YOUR_DOCKER_USERNAME>/app-flask-caminas:1.0
Enter fullscreen mode Exit fullscreen mode

Docker's current documentation uses this same workflow: authenticate, tag the image with the Docker Hub namespace and repository, and then push the tagged image.

Now anyone with access to the repository can use your image with:

docker run -p 5000:5000 <YOUR_DOCKER_USERNAME>/app-flask-caminas:1.0
Enter fullscreen mode Exit fullscreen mode

This will download the image if it isn't on the computer and run it.

Now we can visit:

http://localhost:5000
Enter fullscreen mode Exit fullscreen mode

Final thoughts

Creating our own Docker images is pretty easy. We just need the basics of using the terminal, then we can replicate all the steps in a Dockerfile to create our images, even distributing them via Docker Hub.

The benefits of using our own custom Docker images are:

  • Customization: Creating and using your own Docker images allows for tailored configurations, ensuring that the image contains specific dependencies, settings, and optimizations that align with the requirements of your applications.

  • Security Compliance: By building and managing your own Docker images, we have direct control over the inclusion and configuration of security measures and dependencies.

  • Reduced Image Size: Customizing Docker images allows for the elimination of unnecessary components, resulting in smaller image sizes. Smaller images can lead to faster deployment times and more efficient resource utilization.

  • Flexibility and Specialized Tooling: Building our own Docker images provides the flexibility to incorporate specialized tooling or unique requirements specific to your organization's workflows. This customization lets teams create and deploy applications with the exact environment they need.


Resources

Original post

Docker basics for beginners

Install Docker

GitHub: Flask App

GitHub

Docker Hub

Dockerfile reference

Build, tag, and publish an image

Top comments (0)