Construct Of An Docker Image And Its Building Process
A Docker image is like a box of Oreo. Its final image is like multiple Oreos (layer) stacked on top of each other. A layer could be built from multiple building stages (like our favorite chocolate topping), and each stage consists of smaller layers.
Now Let’s walk through a basic example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Stage 0: Build Stage
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build
# Stage 1: Final Stage
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node", "dist/main.js"]
Layers: Docker images are built in layers.
1
2
3
WORKDIR /app
COPY package*.json ./
RUN npm install --production
- Each layer is an instruction such as
RUN,COPY,ADD, etc. Each layer is stacked on top of each other. The above is a three-layer model.
Stages: The above is a two layer build. Each stage starts from a FROM command, which launches a base image.
- Stage 0 creates artifacts / build of the custom app (e.g.,
RUN npx run build). It consists of several layers -
Stage 1 is created by launching a new base image
node:18-alpineand copying the build artifacts from Stage 0. The final image only contains layers from the last stage and files copied from previous stages. So Stage 0 is effectively discarded. - To use a custom Dockerfile, use the
-farg:docker build -f Dockerfile_test_container . -t ros-noetic-simple-robotics-tests
Create An Entrypoint
An entrypoint is a script that runs once a container starts. Here, we need to copy the entry point to a common location, then execute it
1
2
3
4
5
COPY entrypoint_rgbd_slam_docker.sh /entrypoint_rgbd_slam_docker.sh
# RUN chmod +x /entrypoint_rgbd_slam_docker.sh
ENTRYPOINT ["/entrypoint_rgbd_slam_docker.sh"]
# must follow ENTRYPOINT, otherwise the container won't execute bash
CMD ["bash"]
In entrypoint, make sure the last line has
1
2
3
4
...
# Execute the CMD command (bash) by replacing the shell with the CMD command in Dockerfile
exec "$@"
Changing Python Version to Python 3.10 (Not Recommended For Potential Code Breaking)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Add Deadsnakes PPA and install Python 3.10
RUN apt-get update && apt-get install -y software-properties-common && \
add-apt-repository ppa:deadsnakes/ppa && \
apt-get update && \
apt-get install -y python3.10 python3.10-dev python3.10-venv python3.10-distutils && \
rm -rf /var/lib/apt/lists/*
# Install pip for Python 3.10
RUN wget https://bootstrap.pypa.io/get-pip.py && \
python3.10 get-pip.py && \
rm get-pip.py
# Update alternatives to set python3 to python3.10
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 && \
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 2 && \
update-alternatives --set python3 /usr/bin/python3.10
# Set environment variables for Python
ENV PYTHON_VERSION=3.10
ENV PYTHON_BIN=/usr/bin/python${PYTHON_VERSION}
ENV PIP_BIN=/usr/local/bin/pip
# Upgrade pip and setuptools, then install a compatible PyYAML version
RUN pip3 install --upgrade pip setuptools wheel && \
pip3 install --force-reinstall --ignore-installed PyYAML==6.0
Common Build Issues
- See
exec /usr/bin/sh: exec format error. Try initializing and activating a new buildx builder instance. (referece)buildxenables multi-platform builds in Docker. This is crucial for building arm v8 images on amd64 host.- Default
docker builddoes not support multi-platform builds
1
2
3
4
docker buildx create --use
# To verify:
docker buildx ls
Misc. Docker Build Commands
-fif there’s a different name of a docker file:DOCKER_BUILDKIT=1 docker build -f Dockerfile_mumble_physical_runtime -t mumble-physical-runtime .Dockerfile_mumble_physical_runtimeis the docker file;s name
- Syntaxes
ARGdefines local build time variablesENVare environment variables that will persist throughout the runtime env.
- Build args
- In
docker-compose:1 2 3 4 5 6
build: context: ./mumble_physical_runtime dockerfile: Dockerfile_mumble_physical_runtime args: # - WORKDIRECTORY=/home/mumble_physical_runtime/src - WORKDIRECTORY=/home/mumble_physical_runtime - In
Dockerfile:1
ARG WORKDIRECTORY="TO_GET_FROM_DOCKER_COMPOSE"
- The old values of
WORKDIRECTORYcould be cached, and with the defaultdocker build, the old values could still persist. In that case, we need to build without cached. TODO: I’m not sure what exactly gets cached
- In
Docker Image Management
General workflow includes properly tagging an image, then pushing to dockerhub:
1
2
docker tag dream-rgbd-rico:latest ricojia/dream-rgbd-rico:1.0 #
docker push ricojia/dream-rgbd-rico:1.0
ricojiais my username, also my namespace- latest is the default tag when it’s not provided.
- In the above,
1.0has been properly added to as a tag. Now on Dockerhub,latestand1.0points to the same image
To pull this image,
1
docker pull ricojia/dream-rgbd-rico:latest
- Remove untagged images:
docker image prune -f - Remove images with a common name, e.g.,
ricojia/dream-rgbd-rico*:docker images | grep 'ricojia/dream-rgbd-rico' | awk '{print $1 ":" $2}' | xargs docker rmi
General Guidelines Of Docker Development
- Use Multi-Stage Builds: Multi-stage builds allow you to use multiple
FROMstatements in your Dockerfile. This helps in creating smaller, more efficient images by copying only the necessary artifacts from one stage to another. - Clean Up After Installation: Remove temporary files and package lists after installing software to keep the image size small. For example, use
apt-get cleanandrm -rf /var/lib/apt/lists/*after installing packages. - Use
.dockerignore: Create a.dockerignorefile to exclude unnecessary files and directories from the build context, reducing the build time and image size. - Minimize the Number of Layers: Combine multiple commands into a single
RUNstatement using && to reduce the number of layers. (For production) - Use Non-Root User: Whenever possible, avoid running applications as the root user inside the container. Create a non-root user and switch to it using the USER instruction.
- Health Checks: Use the
HEALTHCHECKinstruction to define how Docker should test the container to check if it’s still working.