1MinDocker #5 - Build and push a Docker image

In the last article we saw how we can pull an image, run it inside a container,

list images and containers and remove them: now it’s time to build, so we’ll create our first simple Docker image.

The Dockerfile

As we already said in our conceptual introduction to Docker, a Dockerfile is a sort of recipe: it contains all the instructions to collect the ingredients (the image) that will make the cake (the container).

But what exactly can a Dockerfile contain? We will see, in our example (that you can find here), the following base key words:

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}


This can be easily overridden by:

docker build . –build-args NODE_VERSION=”18”

- `ENV`: this key word, as the name suggest, sets an _environment_ variable. Environment variables are fixed and cannot be changed at build-time, and they can be useful when we want a variable to be accessible to all image build stages.

### Let's build a Dockerfile

To build a Dockerfile, we need to know what application we are going to ship through the image we're about to set up.

In this tutorial, we will build a very simple python application with [Gradio](https://gradio.app), a popular framework to build elegant and beautiful frontend for AI/ML python apps.

Our folder will look like this:

build_an_image_1/ |__ app.py |__ Dockerfile


To fill up `app.py`, we will use a template that [Hugging Face](https://huggingface.com) itself provides for Gradio ChatBot Spaces:


```python
import gradio as gr


def respond(
    message,
    history):
    message_back = f"Your message is: {message}"
    response = ""
    for m in message_back:
        response += m
        yield response

demo = gr.ChatInterface(
    respond,
    title="Echo Bot",
)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)

This is a simple bot that echoes every message we send. We will just copy this code into our main script, app.py.

Now we’re ready to build our Docker image, starting with modifying our Dockerfile.

1. The base image

For our environment we need Python 3, so we will need to find a suitable base image for that.

Luckily, Python itself provides us with Alpine-based (a Linux distro) python images, so we will just use python:3.11.9.

We just then need to specify:

ARG PYTHON_VERSION="3.11.9"
FROM python:${PYTHON_VERSION}

At the very beginning of our Dockerfile.

As we said, if we want a different python version, we just need to run:

docker build . --build-args PYTHON_VERSION="3.10.14"

2. Get the needed dependencies

Our app depends exclusively on gradio, so we can do a quick pip install for that!

We also set the version (5.4.0) as an ARG and ENV:

ARG GRADIO_V="5.4.0"
ENV GRADIO_VERSION=${GRADIO_V}

RUN python3 -m pip cache purge
RUN python3 -m pip install gradio==${GRADIO_VERSION}

You cannot change GRADIO_VERSION directly, but you can pass GRADIO_V as a build argument and modify also the ENV value!

docker build . --build-args GRADIO_V="5.1.0"

3. Start the application

We need to start the application, something that we would normally do as python3 app.py.

But our app.py file is locally stored, not available to the Docker image, so we need to copy it into our Docker working directory:

WORKDIR /app/
COPY ./app.py /app/

Since our application runs on http://0.0.0.0:7860, we need to expose port 7860:

EXPOSE 7860

Now we can make our application run:

ENTRYPOINT ["python3"]
CMD ["/app/app.py"]

We will not be able to change the base executable (python3) but we will be able to override the CMD instance specifying another path at runtime (for example if we mount a volume while running the container).

4. Full Dockerfile

Our full Dockerfile will look like this:

ARG PYTHON_VERSION="3.11.9"
FROM python:${PYTHON_VERSION}

WORKDIR /app/
COPY ./app.py /app/

ARG GRADIO_V="5.4.0"
ENV GRADIO_VERSION=${GRADIO_V}

RUN python3 -m pip cache purge
RUN python3 -m pip install gradio==${GRADIO_VERSION}

EXPOSE 7860

ENTRYPOINT ["python3"]
CMD ["/app/app.py"]

Now we just need to build the image!

Build and push the image

When we build the image, we need to specify the context, meaning the directory in which our Dockerfile is. For starters, we will also use the -t flag, which specifies the name and tag of our image:

docker build . -t YOUR-USERNAME/gradio-echo-bot:0.0.0 -t YOUR-USERNAME/gradio-echo-bot:latest

As you can see, you can specify multiple tags.

This build, once launched, will take some minutes to complete, and then you will have your images locally!

If you want to make this images available to everyone, you need to login to your Docker account:

docker login -u YOUR-USERNAME --password-stdin

You will be prompted to input the password from your console.

You won’t put your Docker password, but an access token (follow the link for a guide on how to obtain it).

Now let’s push our image to the Docker Hub registry:

docker push YOUR-USERNAME/gradio-echo-bot:0.0.0
docker push YOUR-USERNAME/gradio-echo-bot:latest

The push generally takes some time, but after that our image will be live on Docker Hub: we published our first Docker image!🎉

We will stop here for this article, but in the next one we will dive into more advanced build concepts🥰