{"id":3259,"date":"2023-01-12T09:58:55","date_gmt":"2023-01-12T14:58:55","guid":{"rendered":"https:\/\/dft.wiki\/?p=3259"},"modified":"2026-06-08T17:11:01","modified_gmt":"2026-06-08T21:11:01","slug":"how-to-create-docker-image","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=3259","title":{"rendered":"Creating Docker Images"},"content":{"rendered":"<p>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.<\/p>\n<hr \/>\n<p><strong>MAIN STEPS<\/strong><\/p>\n<ul>\n<li>Create a new directory.<\/li>\n<li>Create a new file in that directory called <code>Dockerfile<\/code>.<\/li>\n<li>Add the following instructions to the file:\n<ul>\n<li><code>FROM<\/code> &#8211; specifies the base image to build from.<\/li>\n<li><code>RUN<\/code> &#8211; executes commands during the image build process.<\/li>\n<li><code>COPY<\/code> \/ <code>ADD<\/code> &#8211; copies files or directories into the image.<\/li>\n<li><code>CMD<\/code> &#8211; specifies the command to run when a container is started from the image.<\/li>\n<\/ul>\n<\/li>\n<li>Build the image using <code>docker build<\/code> with the <code>-t<\/code> flag to assign it a name.<\/li>\n<\/ul>\n<p>Additional instructions can be added as needed:<\/p>\n<ul>\n<li><code>ENV<\/code> &#8211; sets environment variables in the image.<\/li>\n<li><code>EXPOSE<\/code> &#8211; declares which ports the container will listen on at runtime.<\/li>\n<li><code>USER<\/code> &#8211; sets the UID and\/or GID that <code>CMD<\/code> will run as.<\/li>\n<li><code>WORKDIR<\/code> &#8211; sets the working directory for subsequent instructions like <code>RUN<\/code> and <code>CMD<\/code>.<\/li>\n<li><code>VOLUME<\/code> &#8211; creates a persistent volume that survives container restarts.<\/li>\n<li>and more.<\/li>\n<\/ul>\n<p><strong>Note:<\/strong> <code>COPY<\/code> and <code>ADD<\/code> are not the same. <code>COPY<\/code> only copies local files and directories from the build context into the image. <code>ADD<\/code> also supports extracting compressed archives (such as tar or zip files) and copying files from a remote URL.<\/p>\n<hr \/>\n<p><strong>INSTALL LATEST DOCKER FROM OFFICIAL REPOSITORY<\/strong><\/p>\n<pre>sudo apt update\r\nsudo apt install ca-certificates curl\r\nsudo install -m 0755 -d \/etc\/apt\/keyrings\r\nsudo curl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg -o \/etc\/apt\/keyrings\/docker.asc\r\nsudo chmod a+r \/etc\/apt\/keyrings\/docker.asc\r\nsudo nano \/etc\/apt\/sources.list.d\/docker.sources<\/pre>\n<pre>Types: deb\r\nURIs: https:\/\/download.docker.com\/linux\/ubuntu\r\nSuites: $(. \/etc\/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\")\r\nComponents: stable\r\nArchitectures: amd64\r\nSigned-By: \/etc\/apt\/keyrings\/docker.asc<\/pre>\n<pre>sudo apt update\r\nsudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y\r\nsudo systemctl status docker<\/pre>\n<hr \/>\n<p><strong>EXAMPLES<\/strong><\/p>\n<p>Create a file named <code>Dockerfile<\/code> and add the build instructions. See examples below:<\/p>\n<ul>\n<li>Building the latest Alpine.<\/li>\n<\/ul>\n<pre>FROM alpine:latest\r\nCOPY .\/hello.sh \/usr\/local\/bin\/hello\r\nRUN chmod +x \/usr\/local\/bin\/hello\r\nCMD [\"\/usr\/local\/bin\/hello\"]<\/pre>\n<ul>\n<li>Building Python 3.9 on top of Alpine, from its official repository on Docker Hub.<\/li>\n<\/ul>\n<pre>FROM python:3.9-alpine\r\nENV PYTHONUNBUFFERED 1\r\nCOPY . \/app\r\nWORKDIR \/app\r\nRUN pip install --upgrade pip\r\nRUN pip install -r requirements.txt\r\nEXPOSE 80:5000\/tcp\r\nCMD [\"flask\", \"run\", \"--host=0.0.0.0\"]<\/pre>\n<ul>\n<li>Building the latest Uptime Kuma with some additional tools.<\/li>\n<\/ul>\n<pre>FROM louislam\/uptime-kuma\r\nRUN apt update \r\nRUN apt install nano htop curl -y\r\nEXPOSE 3001\/tcp\r\n<\/pre>\n<ul>\n<li>Building PostgreSQL 17.7 on top of Alpine, from its official repository on Docker Hub.<\/li>\n<\/ul>\n<pre>FROM postgres:17.7\r\nENV POSTGRES_DB dbHost\r\nENV POSTGRES_USER dbUser\r\nENV POSTGRES_PASSWORD dbPassword\r\nCOPY init.sql \/docker-entrypoint-initdb.d\/\r\nEXPOSE 5432<\/pre>\n<ul>\n<li>Building a generic Red Hat 10 image using the Universal Base Image from Red Hat&#8217;s public repo.<\/li>\n<\/ul>\n<pre>FROM registry.access.redhat.com\/ubi10\/ubi:latest\r\nRUN dnf install -y httpd && \\\r\n    dnf clean all && \\\r\n    rm -rf \/var\/cache\/dnf\r\nENV CACHE=\/tmp\/cache\r\nRUN mkdir -p \/devenv\r\nADD . \/devenv\r\nWORKDIR \/devenv\r\nRUN \/devenv\/script.sh\r\nEXPOSE 8080:80\r\nCMD [\"\/dev\/entry-point.sh\"]<\/pre>\n<ul>\n<li>Building a Red Hat 10 image using the <strong>Universal Base Image with Init<\/strong> from Red Hat&#8217;s public repo.<\/li>\n<\/ul>\n<pre>FROM registry.access.redhat.com\/ubi10\/ubi-init\r\nRUN dnf install -y --nodocs --setopt install_weak_deps=false http mariadb && \\\r\n    dnf clean all\r\nEXPOSE 80 3306\r\nRUN systemctl enable httpd mariadb<\/pre>\n<ul>\n<li>Building a Fedora image and adding an additional repository.<\/li>\n<\/ul>\n<pre>FROM fedora:latest\r\nENV LIBGUESTFS_BACKEND=direct\r\nRUN dnf -y install virt-v2v dnf-plugins-core wget\r\nRUN wget https:\/\/fedorapeople.org\/groups\/virt\/virtio-win\/virtio-win.repo -O \/etc\/yum.repos.d\/virtio-win.repo && \\\r\n    dnf -y install virtio-win\r\nRUN dnf clean all && \\\r\n    rm -rf \/var\/cache\/dnf\r\n<\/pre>\n<ul>\n<li>Building a container to establish a Tunnel (also known as Argo or Connector) with Cloudflare Zero Trust.<\/li>\n<\/ul>\n<pre>FROM debian:12\r\nRUN apt update\r\nRUN apt install nano htop curl wget -y\r\nRUN wget -O \/tmp\/cloudflared.deb -q https:\/\/github.com\/cloudflare\/cloudflared\/releases\/latest\/download\/cloudflared-linux-amd64.deb\r\nRUN dpkg -i \/tmp\/cloudflared.deb && rm \/tmp\/cloudflared.deb\r\nCMD [\"sh\", \"-c\", \"\/usr\/bin\/cloudflared --no-autoupdate tunnel run --token <CLOUDFLARE>\"]<\/pre>\n<p><strong>BUILD<\/strong><\/p>\n<pre>sudo docker build -t <strong>customImageName:v1.0<\/strong> .<\/pre>\n<p><strong>RUN<\/strong><\/p>\n<pre>sudo docker run --name <strong>containerName<\/strong> customImageName:v1.0<\/pre>\n<ul>\n<li>Building a Debian 13 image with Apache and PHP 8, using a base target and a development target with debugging tools.<\/li>\n<\/ul>\n<pre>FROM php:8-apache-trixie AS <strong>base<\/strong>\r\nRUN a2enmod rewrite\r\nWORKDIR \/var\/www\/\r\nEXPOSE 80\/tcp\r\nCMD [\"apache2-foreground\"]\r\n\r\nFROM base AS <strong>dev<\/strong>\r\nRUN apt update && apt install -y curl nano htop\r\nFROM base AS final<\/pre>\n<p><strong>RUN STAGE<\/strong><\/p>\n<pre>sudo docker run <strong>--target base<\/strong> customImageName<\/pre>\n<hr \/>\n<p><strong>PUBLISHING IMAGES<\/strong><\/p>\n<p>Images (also referred to as Artifacts) can be published to a remote registry for public or private use, with access control support.<\/p>\n<p>The two most popular container registries are <strong>Docker Hub<\/strong> and <strong>GitHub Container Registry<\/strong>. See the examples below for pushing images to both using <strong>GitHub Actions<\/strong>.<\/p>\n<ul>\n<li>Docker Hub [<a href=\"https:\/\/hub.docker.com\/\">Link<\/a>]\n<ul>\n<li><strong>Pros:<\/strong> The most widely used registry and the default for Docker tooling and many third-party tools.<\/li>\n<li><strong>Cons:<\/strong> Pull rate limits can break CI\/CD pipelines and automated deployments.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<pre>name: Build and Push Multi-Arch Docker Image\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - main\r\n\r\njobs:\r\n  build-and-push:\r\n    runs-on: ubuntu-latest\r\n\r\n    steps:\r\n    - name: Checkout code\r\n      uses: actions\/checkout@v3\r\n\r\n    - name: Set up Docker Buildx\r\n      uses: docker\/setup-buildx-action@v2\r\n\r\n    - name: Log in to Docker Hub\r\n      uses: docker\/login-action@v2\r\n      with:\r\n        username: ${{ secrets.DOCKER_USERNAME }}\r\n        password: ${{ secrets.DOCKER_PASSWORD }}\r\n\r\n    - name: Build and push multi-arch image\r\n      uses: docker\/build-push-action@v5\r\n      with:\r\n        context: .\r\n        platforms: linux\/amd64,linux\/arm64\r\n        push: true\r\n        tags: ${{ secrets.DOCKER_USERNAME }}\/${{ github.event.repository.name }}:latest<\/pre>\n<ul>\n<li>GitHub Container Registry [<a href=\"https:\/\/ghcr.io\">Link<\/a>]\n<ul>\n<li><strong>Pros:<\/strong> Tight integration with GitHub repositories, permissions, and GitHub Actions.<\/li>\n<li><strong>Cons:<\/strong> Not the default registry in many tools, which can lead to accidentally pulling images from Docker Hub instead of GHCR.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<pre>name: Build and Push Multi-Arch to GHCR\r\n\r\non:\r\n  workflow_dispatch:\r\n\r\npermissions:\r\n  contents: read\r\n  packages: write\r\n\r\njobs:\r\n  build-and-push:\r\n    runs-on: ubuntu-latest\r\n\r\n    steps:\r\n      - name: Checkout code\r\n        uses: actions\/checkout@v3\r\n\r\n      - name: Set up Docker Buildx\r\n        uses: docker\/setup-buildx-action@v2\r\n\r\n      - name: Log in to GHCR\r\n        uses: docker\/login-action@v2\r\n        with:\r\n          registry: ghcr.io\r\n          username: ${{ github.actor }}\r\n          password: ${{ secrets.GITHUB_TOKEN }}\r\n\r\n      - name: Build and push multi-arch image\r\n        uses: docker\/build-push-action@v5\r\n        with:\r\n          context: .\r\n          platforms: linux\/amd64,linux\/arm64\r\n          push: true\r\n          tags: ghcr.io\/${{ github.repository_owner }}\/${{ github.event.repository.name }}:latest<\/pre>\n<p><strong>Note:<\/strong> 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.<\/p>\n<p>See more examples at Docker Awesome Compose [<a href=\"https:\/\/github.com\/docker\/awesome-compose\">Link<\/a>].<\/p>\n<hr \/>\n<p><strong>EXTRACT IMAGE CONTENT<\/strong><\/p>\n<p>To extract the contents of a Docker <span style=\"text-decoration: underline;\">Image<\/span> for analysis:<\/p>\n<pre>sudo docker save USER\/IMAGE > USER_IMAGE.tar\r\ntar xvf USER_IMAGE.tar<\/pre>\n<p>To extract from a Docker <span style=\"text-decoration: underline;\">Container<\/span>:<\/p>\n<pre>sudo docker export CONTAINER > CONTAINER.tar\r\ntar xvf USER_IMAGE.tar<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Having a custom image allows applications to be scaled on-demand via orchestration tools (Kubernetes, Rancher, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-3259","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/3259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3259"}],"version-history":[{"count":18,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/3259\/revisions"}],"predecessor-version":[{"id":5657,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/3259\/revisions\/5657"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}