Having a custom image allows applications to be scaled on-demand via orchestration tools (Kubernetes, Rancher, Kasm, etc.) because containers can be spun up in milliseconds straight from an image.
MAIN STEPS
- Create a new directory.
- Create a new file in that directory called
Dockerfile. - Add the following instructions to the file:
FROM– specifies the base image to build from.RUN– executes commands during the image build process.COPY/ADD– copies files or directories into the image.CMD– specifies the command to run when a container is started from the image.
- Build the image using
docker buildwith the-tflag to assign it a name.
Additional instructions can be added as needed:
ENV– sets environment variables in the image.EXPOSE– declares which ports the container will listen on at runtime.USER– sets the UID and/or GID thatCMDwill run as.WORKDIR– sets the working directory for subsequent instructions likeRUNandCMD.VOLUME– creates a persistent volume that survives container restarts.- and more.
Note: COPY and ADD are not the same. COPY only copies local files and directories from the build context into the image. ADD also supports extracting compressed archives (such as tar or zip files) and copying files from a remote URL.
INSTALL LATEST DOCKER FROM OFFICIAL REPOSITORY
sudo apt update sudo apt install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc sudo nano /etc/apt/sources.list.d/docker.sources
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.asc
sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y sudo systemctl status docker
EXAMPLES
Create a file named Dockerfile and add the build instructions. See examples below:
- Building the latest Alpine.
FROM alpine:latest COPY ./hello.sh /usr/local/bin/hello RUN chmod +x /usr/local/bin/hello CMD ["/usr/local/bin/hello"]
- Building Python 3.9 on top of Alpine, from its official repository on Docker Hub.
FROM python:3.9-alpine ENV PYTHONUNBUFFERED 1 COPY . /app WORKDIR /app RUN pip install --upgrade pip RUN pip install -r requirements.txt EXPOSE 80:5000/tcp CMD ["flask", "run", "--host=0.0.0.0"]
- Building the latest Uptime Kuma with some additional tools.
FROM louislam/uptime-kuma RUN apt update RUN apt install nano htop curl -y EXPOSE 3001/tcp
- Building PostgreSQL 17.7 on top of Alpine, from its official repository on Docker Hub.
FROM postgres:17.7 ENV POSTGRES_DB dbHost ENV POSTGRES_USER dbUser ENV POSTGRES_PASSWORD dbPassword COPY init.sql /docker-entrypoint-initdb.d/ EXPOSE 5432
- Building a generic Red Hat 10 image using the Universal Base Image from Red Hat’s public repo.
FROM registry.access.redhat.com/ubi10/ubi:latest
RUN dnf install -y httpd && \
dnf clean all && \
rm -rf /var/cache/dnf
ENV CACHE=/tmp/cache
RUN mkdir -p /devenv
ADD . /devenv
WORKDIR /devenv
RUN /devenv/script.sh
EXPOSE 8080:80
CMD ["/dev/entry-point.sh"]
- Building a Red Hat 10 image using the Universal Base Image with Init from Red Hat’s public repo.
FROM registry.access.redhat.com/ubi10/ubi-init
RUN dnf install -y --nodocs --setopt install_weak_deps=false http mariadb && \
dnf clean all
EXPOSE 80 3306
RUN systemctl enable httpd mariadb
- Building a Fedora image and adding an additional repository.
FROM fedora:latest
ENV LIBGUESTFS_BACKEND=direct
RUN dnf -y install virt-v2v dnf-plugins-core wget
RUN wget https://fedorapeople.org/groups/virt/virtio-win/virtio-win.repo -O /etc/yum.repos.d/virtio-win.repo && \
dnf -y install virtio-win
RUN dnf clean all && \
rm -rf /var/cache/dnf
- Building a container to establish a Tunnel (also known as Argo or Connector) with Cloudflare Zero Trust.
FROM debian:12 RUN apt update RUN apt install nano htop curl wget -y RUN wget -O /tmp/cloudflared.deb -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb RUN dpkg -i /tmp/cloudflared.deb && rm /tmp/cloudflared.deb CMD ["sh", "-c", "/usr/bin/cloudflared --no-autoupdate tunnel run --token"]
BUILD
sudo docker build -t customImageName:v1.0 .
RUN
sudo docker run --name containerName customImageName:v1.0
- Building a Debian 13 image with Apache and PHP 8, using a base target and a development target with debugging tools.
FROM php:8-apache-trixie AS base RUN a2enmod rewrite WORKDIR /var/www/ EXPOSE 80/tcp CMD ["apache2-foreground"] FROM base AS dev RUN apt update && apt install -y curl nano htop FROM base AS final
RUN STAGE
sudo docker run --target base customImageName
PUBLISHING IMAGES
Images (also referred to as Artifacts) can be published to a remote registry for public or private use, with access control support.
The two most popular container registries are Docker Hub and GitHub Container Registry. See the examples below for pushing images to both using GitHub Actions.
- Docker Hub [Link]
- Pros: The most widely used registry and the default for Docker tooling and many third-party tools.
- Cons: Pull rate limits can break CI/CD pipelines and automated deployments.
name: Build and Push Multi-Arch Docker Image
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
- GitHub Container Registry [Link]
- Pros: Tight integration with GitHub repositories, permissions, and GitHub Actions.
- Cons: Not the default registry in many tools, which can lead to accidentally pulling images from Docker Hub instead of GHCR.
name: Build and Push Multi-Arch to GHCR
on:
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push multi-arch image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
Note: Git repositories allow uppercase letters in their names, but container registries do not. If your repository name contains uppercase characters, image pushes may fail rather than being automatically converted to lowercase. Keep this in mind when naming your repositories.
See more examples at Docker Awesome Compose [Link].
EXTRACT IMAGE CONTENT
To extract the contents of a Docker Image for analysis:
sudo docker save USER/IMAGE > USER_IMAGE.tar tar xvf USER_IMAGE.tar
To extract from a Docker Container:
sudo docker export CONTAINER > CONTAINER.tar tar xvf USER_IMAGE.tar