{"id":3259,"date":"2023-01-12T09:58:55","date_gmt":"2023-01-12T14:58:55","guid":{"rendered":"https:\/\/dft.wiki\/?p=3259"},"modified":"2026-02-22T22:22:19","modified_gmt":"2026-02-23T03:22:19","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 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.<\/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 blocks to the new file:\n<ul>\n<li><code>FROM<\/code> to use a specific basic image.<\/li>\n<li><code>RUN<\/code>\u00a0to execute commands during the image build process.<\/li>\n<li><code>COPY<\/code> \/ <code>ADD<\/code> to copy files or directories to the image.<\/li>\n<li><code>CMD<\/code> to specify the command that will be executed when a container is run from the image.<\/li>\n<\/ul>\n<\/li>\n<li>Build the new image (with <code>docker build<\/code> and <code>-t<\/code> to give it a name).<\/li>\n<\/ul>\n<p>There are additional instructions that can be added as needed, such as:<\/p>\n<ul>\n<li><code>ENV<\/code> to set environment variables in the image.<\/li>\n<li><code>EXPOSE<\/code> to declare what ports the container will listen to at runtime.<\/li>\n<li><code>USER<\/code> is used to set the UID and\/or GID that the command, run by <code>CMD<\/code>, should be executed as.<\/li>\n<li><code>WORKDIR<\/code> defines the working directory for subsequent instructions, such as <code>RUN<\/code> and <code>CMD<\/code>.<\/li>\n<li><code>VOLUME<\/code> creates a persistent volume in the image (to survive 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. Essentially, <code>COPY<\/code> only copies local files and directories from the build context to the image. While <code>ADD<\/code> also supports automatically extracting files from a compressed archive (such as a tar or zip file) 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 &amp;&amp; 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 with the name <code>Dockerfile<\/code> and add the build instructions. See examples:<\/p>\n<ul>\n<li>Building 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, specifying version 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 and adding 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, specifying version 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 RedHat 10 image using the Universal Base Image from RedHat&#8217;s public repo.<\/li>\n<\/ul>\n<pre>FROM registry.access.redhat.com\/ubi10\/ubi:latest\r\nRUN dnf install -y httpd &amp;&amp; \\\r\n    dnf clean all &amp;&amp; \\\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 RedHat 10 image using the <strong>Universal Base Image with Init<\/strong> from RedHat&#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 &amp;&amp; \\\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 installing an additional repository into it.<\/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 &amp;&amp; \\\r\n    dnf -y install virtio-win\r\nRUN dnf clean all &amp;&amp; \\\r\n    rm -rf \/var\/cache\/dnf\r\n<\/pre>\n<ul>\n<li>Building a container to establish a Tunnel (aka Argo or Connector) with Cloudflare&#8217;s ZeroTrust.<\/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 &amp;&amp; rm \/tmp\/cloudflared.deb\r\nCMD [\"sh\", \"-c\", \"\/usr\/bin\/cloudflared --no-autoupdate tunnel run --token &lt;CLOUDFLARE&gt;\"]<\/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 set up with 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 &amp;&amp; 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, aka Artifacts, can be published to a remote Registry for public or private (with access control) use.<\/p>\n<p>The two most popular container repositories 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> Most widely used registry and the default for Docker tooling and many third-party tools.<\/li>\n<li><strong>Cons:<\/strong> Recent 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 assumed 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 you use uppercase characters, image pushes may fail instead of being automatically converted to lowercase. Keep this in mind.<\/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>Sometimes it is needed to extract the content of a Docker <span style=\"text-decoration: underline;\">Image<\/span> for analysis.<\/p>\n<pre>sudo docker save USER\/IMAGE &gt; USER_IMAGE.tar\r\ntar xvf USER_IMAGE.tar<\/pre>\n<p>Or from a Docker <span style=\"text-decoration: underline;\">Container<\/span>:<\/p>\n<pre>sudo docker export CONTAINER &gt; CONTAINER.tar\r\ntar xvf USER_IMAGE.tar<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Having a custom image would allow one to scale applications on-demand via orchestration (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":17,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/3259\/revisions"}],"predecessor-version":[{"id":5342,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/3259\/revisions\/5342"}],"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}]}}