Having a custom image would allow one to scale applications on-demand via orchestration (Kubernetes, Rancher, Kasm, etc) because they 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 blocks to the new file:
FROMto use a specific basic image.RUNto execute commands during the image build process.COPY/ADDto copy files or directories to the image.CMDto specify the command that will be executed when a container is run from the image.
- Build the new image (with
docker buildand-tto give it a name).
There are additional instructions that can be added as needed, such as:
ENVto set environment variables in the image.EXPOSEto declare what ports the container will listen to at runtime.USERis used to set the UID and/or GID that the command, run byCMD, should be executed as.WORKDIRdefines the working directory for subsequent instructions, such asRUNandCMD.VOLUMEcreates a persistent volume in the image (to survive container restarts).- and more.
Note: COPY and ADD are not the same. Essentially, COPY only copies local files and directories from the build context to the image. While ADD also supports automatically extracting files from a compressed archive (such as a tar or zip file) 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 with the name Dockerfile and add the build instructions. See examples:
- Building 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, specifying version 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 and adding some additional tools.
FROM louislam/uptime-kuma RUN apt update RUN apt install nano htop curl -y EXPOSE 3001/tcp
- Building PostgreSQL, specifying version 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 RedHat 10 image using the Universal Base Image from RedHat’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 RedHat 10 image using the Universal Base Image with Init from RedHat’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 installing an additional repository into it.
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 (aka Argo or Connector) with Cloudflare’s ZeroTrust.
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 <CLOUDFLARE>"]
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 set up with 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, aka Artifacts, can be published to a remote Registry for public or private (with access control) use.
The two most popular container repositories are Docker Hub and GitHub Container Registry. See the examples below for pushing images to both using GitHub Actions.
- Docker Hub [Link]
- Pros: Most widely used registry and the default for Docker tooling and many third-party tools.
- Cons: Recent 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 assumed 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 you use uppercase characters, image pushes may fail instead of being automatically converted to lowercase. Keep this in mind.
See more examples at Docker Awesome Compose [Link].
EXTRACT IMAGE CONTENT
Sometimes it is needed to extract the content of a Docker Image for analysis.
sudo docker save USER/IMAGE > USER_IMAGE.tar tar xvf USER_IMAGE.tar
Or from a Docker Container:
sudo docker export CONTAINER > CONTAINER.tar tar xvf USER_IMAGE.tar