392

Is it possible to generate a Dockerfile from an image? I want to know for two reasons:

  1. I can download images from the repository but would like to see the recipe that generated them.

  2. I like the idea of saving snapshots, but once I am done it would be nice to have a structured format to review what was done.

Jugal Shah
  • 3,621
  • 1
  • 24
  • 35
user1026169
  • 5,345
  • 5
  • 21
  • 35
  • 1
    You can use Portainer.io https://portainer.io/ It's a web app that runs inside a docker container used to manage all (almost) stuff about your containers. Even images recepies. – Vincent Jun 06 '18 at 03:16

11 Answers11

251

How to generate or reverse a Dockerfile from an image?

You can. Mostly.

Notes: It does not generate a Dockerfile that you can use directly with docker build; the output is just for your reference.

alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage"
dfimage -sV=1.36 nginx:latest

It will pull the target docker image automatically and export Dockerfile. Parameter -sV=1.36 is not always required.

Reference: https://hub.docker.com/r/alpine/dfimage

Now hub.docker.com shows the image layers with detail commands directly, if you choose a particular tag.

enter image description here

Bonus

If you want to know which files are changed in each layer

alias dive="docker run -ti --rm  -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive"
dive nginx:latest

enter image description here

On the left, you see each layer's command, on the right (jump with tab), the yellow line is the folder that some files are changed in that layer

(Use SPACE to collapse dir)

Old answer

below is the old answer, it doesn't work any more.

$ docker pull centurylink/dockerfile-from-image
$ alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm centurylink/dockerfile-from-image"
$ dfimage --help
Usage: dockerfile-from-image.rb [options] <image_id>
    -f, --full-tree                  Generate Dockerfile for all parent layers
    -h, --help                       Show this message
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
BMW
  • 42,880
  • 12
  • 99
  • 116
  • @kytwb Is it already possible to generate a total right dockerfile from an image? Because the first answer told me it's not containing everything. –  Jan 21 '16 at 08:19
  • 3
    @jenson it is not exact same, can cover 95%. But doesn't work with squashed image. – BMW Jan 21 '16 at 10:17
  • @BMW thanks, but when you create an image of a running container. Than it's exactly the same? I've an issue where writing a Dockerfile will be to difficult (surely in the beginning) because it's a long and complex installation. We try to do it in the running base-container itself and afterthat commit the container and creating an image of it. –  Jan 21 '16 at 12:17
  • 5
    @BMW Could you please help to resolve this problem running the image from your example? /usr/lib/ruby/gems/2.2.0/gems/excon-0.45.4/lib/excon/unix_socket.rb:14:in `connect_nonblock': Connection refused - connect(2) for /var/run/docker.sock (Errno::ECONNREFUSED) (Excon::Errors::SocketError) – long May 18 '16 at 15:52
  • @long Same issue. did you find any solution? – Yuva Raj Mar 09 '17 at 12:15
  • @YuvaRaj nah, sorry ... we were recreated Dockerfile from scratch :( – long Mar 10 '17 at 13:12
  • 11
    centurylink/dockerfile-from-image doesn't work with new version docker. This one works for me: https://hub.docker.com/r/chenzj/dfimage/ – aleung Jul 26 '17 at 11:27
  • centurylink/dockerfile-from-image way no longer works. Did not try the website. – leeman24 Aug 22 '19 at 18:51
  • 6
    imagelayers.io seems to be broken. It can't find any image including its demo ones – Robert Lugg Dec 10 '19 at 22:28
  • worked for me, thanks! Note that the Dockerfile you're looking for is defined in the second command, `dfimage -sV=1.36 {DESIRED_IMAGE}` – monkut Sep 23 '20 at 02:23
  • where i could see the created Dockerfile? – ARUN Jun 18 '21 at 11:34
  • 2
    it gives me some output but it doesn't show `FROM` part that I'm interested. Have I missed something or this can't be done? – xbmono Jul 07 '21 at 00:15
  • 1
    understood. The part of FROM can't be restored. If you check the online tag of nginx:latest in hub.docker.com, it has one line on top: `ADD file:4903a19c327468b0e08e4f463cfc162c66b85b4618b5803d71365862f6302e0b in /`, but still can't link to any exist image directly – BMW Jul 07 '21 at 03:29
189

To understand how a docker image was built, use the docker history --no-trunc command.

You can build a docker file from an image, but it will not contain everything you would want to fully understand how the image was generated. Reasonably what you can extract is the MAINTAINER, ENV, EXPOSE, VOLUME, WORKDIR, ENTRYPOINT, CMD, and ONBUILD parts of the dockerfile.

The following script should work for you:

#!/bin/bash
docker history --no-trunc "$1" | \
sed -n -e 's,.*/bin/sh -c #(nop) \(MAINTAINER .*[^ ]\) *0 B,\1,p' | \
head -1
docker inspect --format='{{range $e := .Config.Env}}
ENV {{$e}}
{{end}}{{range $e,$v := .Config.ExposedPorts}}
EXPOSE {{$e}}
{{end}}{{range $e,$v := .Config.Volumes}}
VOLUME {{$e}}
{{end}}{{with .Config.User}}USER {{.}}{{end}}
{{with .Config.WorkingDir}}WORKDIR {{.}}{{end}}
{{with .Config.Entrypoint}}ENTRYPOINT {{json .}}{{end}}
{{with .Config.Cmd}}CMD {{json .}}{{end}}
{{with .Config.OnBuild}}ONBUILD {{json .}}{{end}}' "$1"

I use this as part of a script to rebuild running containers as images: https://github.com/docbill/docker-scripts/blob/master/docker-rebase

The Dockerfile is mainly useful if you want to be able to repackage an image.

The thing to keep in mind, is a docker image can actually just be the tar backup of a real or virtual machine. I have made several docker images this way. Even the build history shows me importing a huge tar file as the first step in creating the image...

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
user6856
  • 2,309
  • 1
  • 14
  • 5
96

I somehow absolutely missed the actual command in the accepted answer, so here it is again, bit more visible in its own paragraph, to see how many people are like me

$ docker history --no-trunc <IMAGE_ID>
user7610
  • 25,267
  • 15
  • 124
  • 150
  • 1
    So why do we need `ub.docker.com/r/chenzj/dfimage`? It's even a more recent answer. – lucid_dreamer Jan 26 '18 at 06:21
  • 3
    I guess because `docker history` prints the Dockerfile lines in a reverse order and it drops the `RUN` instructions (you get only the command itself, not the `RUN` keyworkd in front of it) and other stuff, so you need to edit it manually to get to a buildable Dockerfile. That other tool may do this editing automatically for you (I did not try it, so I don't know.) – user7610 Jan 26 '18 at 09:28
  • 1
    @user7610 your command just show history of pulled image from hub. How could I see my commands on docker images? – BarzanHayati Oct 12 '19 at 06:02
  • @BarzanHayati Can you ask it as a new question and link it here? Be very specific when asking. Show the command to start the image, then issue some commands you want to later see, as an example, then stop the container (if that's what you actually do in reality), and then ask how to retrieve the issued commands. Thanks. – user7610 Oct 12 '19 at 12:00
  • 2
    @user7610 I could ask it, but as soon as I asked it, I must delete it because other users give me minus points for repeated question. – BarzanHayati Oct 12 '19 at 18:32
  • @BarzanHayati My point is. If you want to recover commands used to build the image, the answers here should give you that. Therefore you are asking something else. Are you starting the container with `docker --rm -it fedora:30 /bin/bash`, then typing some commands into the interactive shell, and then want to recall those interactive commands? That is the only other possibility I can think of. Or maybe you are doing `docker exec` against already running image. – user7610 Oct 12 '19 at 19:04
  • @user7610 I'll check it. – BarzanHayati Oct 14 '19 at 08:31
  • Check what? You should already know if you are doing what I've described, or something else. – user7610 Oct 14 '19 at 18:20
83

A bash solution:

docker history --no-trunc $argv | tac | tr -s ' ' | cut -d " " -f 5- | sed 's,^/bin/sh -c #(nop) ,,g' | sed 's,^/bin/sh -c,RUN,g' | sed 's, && ,\n  & ,g' | sed 's,\s*[0-9]*[\.]*[0-9]*\s*[kMG]*B\s*$,,g' | head -n -1

Step by step explanations:

tac : reverse the file
tr -s ' '                                       trim multiple whitespaces into 1
cut -d " " -f 5-                                remove the first fields (until X months/years ago)
sed 's,^/bin/sh -c #(nop) ,,g'                  remove /bin/sh calls for ENV,LABEL...
sed 's,^/bin/sh -c,RUN,g'                       remove /bin/sh calls for RUN
sed 's, && ,\n  & ,g'                           pretty print multi command lines following Docker best practices
sed 's,\s*[0-9]*[\.]*[0-9]*\s*[kMG]*B\s*$,,g'   remove layer size information
head -n -1                                      remove last line ("SIZE COMMENT" in this case)

Example:

 ~  dih ubuntu:18.04
ADD file:28c0771e44ff530dba3f237024acc38e8ec9293d60f0e44c8c78536c12f13a0b in /
RUN set -xe
   &&  echo '#!/bin/sh' > /usr/sbin/policy-rc.d
   &&  echo 'exit 101' >> /usr/sbin/policy-rc.d
   &&  chmod +x /usr/sbin/policy-rc.d
   &&  dpkg-divert --local --rename --add /sbin/initctl
   &&  cp -a /usr/sbin/policy-rc.d /sbin/initctl
   &&  sed -i 's/^exit.*/exit 0/' /sbin/initctl
   &&  echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
   &&  echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean
   &&  echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean
   &&  echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean
   &&  echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages
   &&  echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes
   &&  echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests
RUN rm -rf /var/lib/apt/lists/*
RUN sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list
RUN mkdir -p /run/systemd
   &&  echo 'docker' > /run/systemd/container
CMD ["/bin/bash"]
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
fallino
  • 1,381
  • 11
  • 9
  • This doesn't add a trailing backslash when it breaks up multiline RUN statements. I've added my own answer inspired by this. – Scott Centoni Dec 01 '18 at 19:14
  • tac is not available on mac , so you can go for awk like below: | awk '{print NR,$0}' | sort -nr | sed 's/^[0-9]* //'| – phulei May 13 '20 at 10:35
  • I vote for this because it works with podman (and no docker installed in the system) – Kirill Taran Sep 10 '20 at 15:22
  • 1
    tac for the mac: `tac() { awk '{ l[NR]=$0} END {for(i=NR;i>0;--i)print l[i];}' "$@";}` . Compared to phulei's solution, this is more memory efficient and slightly less overall CPU time. – Otheus Mar 14 '22 at 10:15
  • 2
    Also on a Mac, replace `head -n -1` with `sed '$p'` – Otheus Mar 14 '22 at 10:18
20

Update Dec 2018 to BMW's answer

chenzj/dfimage - as described on hub.docker.com regenerates Dockerfile from other images. So you can use it as follows:

docker pull chenzj/dfimage
alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm chenzj/dfimage"
dfimage IMAGE_ID > Dockerfile
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
AndrewD
  • 4,924
  • 3
  • 30
  • 32
  • 4
    does not seem to work on 19.03.8: `docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"cc6cb8df58e2\": executable file not found in $PATH": unknown.` – Trevor Hickey Jul 23 '20 at 04:01
  • 1
    @TrevorHickey I had the same problem. Did you rename `chenzj/dfimage` to your requested docker image? – 404pio Sep 03 '20 at 07:36
  • 1
    Also where's the git repo for this? – Jason Dec 02 '21 at 04:11
16

If you are interested in an image that is in the Docker hub registry and wanted to take a look at Dockerfile?.

Example:

If you want to see the Dockerfile of image "jupyter/datascience-notebook" type the word "Dockerfile" in the address bar of your browser as shown below.

https://hub.docker.com/r/jupyter/datascience-notebook/ enter image description here

https://hub.docker.com/r/jupyter/datascience-notebook/Dockerfile

enter image description here

Note: Not all the images have Dockerfile, for example, https://hub.docker.com/r/redislabs/redisinsight/Dockerfile Sometimes this way is much faster than searching for Dockerfile in Github.

Kharthigeyan
  • 555
  • 4
  • 14
  • 1
    Super useful solution! Too bad you have to read past a bunch of cumbersome solutions to get here (more upvotes will change that). – D. Woods Dec 11 '22 at 23:28
13

This is derived from @fallino's answer, with some adjustments and simplifications by using the output format option for docker history. Since macOS and Gnu/Linux have different command-line utilities, a different version is necessary for Mac. If you only need one or the other, you can just use those lines.

#!/bin/bash
case "$OSTYPE" in
    linux*)
        docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
        tac                                                    | # reverse the file
        sed 's,^\(|3.*\)\?/bin/\(ba\)\?sh -c,RUN,'             | # change /bin/(ba)?sh calls to RUN
        sed 's,^RUN #(nop) *,,'                                | # remove RUN #(nop) calls for ENV,LABEL...
        sed 's,  *&&  *, \\\n \&\& ,g'                           # pretty print multi command lines following Docker best practices
    ;;
    darwin*)
        docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
        tail -r                                                | # reverse the file
        sed -E 's,^(\|3.*)?/bin/(ba)?sh -c,RUN,'               | # change /bin/(ba)?sh calls to RUN
        sed 's,^RUN #(nop) *,,'                                | # remove RUN #(nop) calls for ENV,LABEL...
        sed $'s,  *&&  *, \\\ \\\n \&\& ,g'                      # pretty print multi command lines following Docker best practices
    ;;
    *)
        echo "unknown OSTYPE: $OSTYPE"
    ;;
esac
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
Scott Centoni
  • 1,019
  • 11
  • 13
12

It is not possible at this point (unless the author of the image explicitly included the Dockerfile).

However, it is definitely something useful! There are two things that will help to obtain this feature.

  1. Trusted builds (detailed in this docker-dev discussion
  2. More detailed metadata in the successive images produced by the build process. In the long run, the metadata should indicate which build command produced the image, which means that it will be possible to reconstruct the Dockerfile from a sequence of images.
jpetazzo
  • 14,874
  • 3
  • 43
  • 45
12
docker pull chenzj/dfimage

alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm chenzj/dfimage"

dfimage image_id

Below is the output of the dfimage command:

$ dfimage 0f1947a021ce

FROM node:8
WORKDIR /usr/src/app

COPY file:e76d2e84545dedbe901b7b7b0c8d2c9733baa07cc821054efec48f623e29218c in ./
RUN /bin/sh -c npm install
COPY dir:a89a4894689a38cbf3895fdc0870878272bb9e09268149a87a6974a274b2184a in .

EXPOSE 8080
CMD ["npm" "start"]
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
user128364
  • 4,533
  • 3
  • 20
  • 12
0

it is possible in just two step. First pull the image then run docker history command. also, shown in SS.

docker pull kalilinux/kali-rolling
docker history --format "{{.CreatedBy}}" kalilinux/kali-rolling --no-trunc

enter image description here

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
linux.cnf
  • 519
  • 6
  • 7
-2

What is image2df

image2df is tool for Generate Dockerfile by an image.

This tool is very useful when you only have docker image and need to generate a Dockerfile whit it.

How does it work

Reverse parsing by history information of an image.

How to use this image

# Command alias
echo "alias image2df='docker run -v /var/run/docker.sock:/var/run/docker.sock --rm cucker/image2df'" >> ~/.bashrc
. ~/.bashrc

# Excute command
image2df <IMAGE>
  • See help

    docker run --rm cucker/image2df --help
    
  • For example

    $ echo "alias image2df='docker run -v /var/run/docker.sock:/var/run/docker.sock --rm cucker/image2df'" >> ~/.bashrc
    $ . ~/.bashrc
    $ docker pull mysql
    $ image2df mysql
    
    ========== Dockerfile ==========
    FROM mysql:latest
    RUN groupadd -r mysql && useradd -r -g mysql mysql
    RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/*
    ENV GOSU_VERSION=1.12
    RUN set -eux; \
        savedAptMark="$(apt-mark showmanual)"; \
        apt-get update; \
        apt-get install -y --no-install-recommends ca-certificates wget; \
        rm -rf /var/lib/apt/lists/*; \
        dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
        wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
        wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
        export GNUPGHOME="$(mktemp -d)"; \
        gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
        gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
        gpgconf --kill all; \
        rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
        apt-mark auto '.*' > /dev/null; \
        [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
        apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
        chmod +x /usr/local/bin/gosu; \
        gosu --version; \
        gosu nobody true
    RUN mkdir /docker-entrypoint-initdb.d
    RUN apt-get update && apt-get install -y --no-install-recommends \
            pwgen \
            openssl \
            perl \
            xz-utils \
        && rm -rf /var/lib/apt/lists/*
    RUN set -ex; \
        key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \
        export GNUPGHOME="$(mktemp -d)"; \
        gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
        gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \
        gpgconf --kill all; \
        rm -rf "$GNUPGHOME"; \
        apt-key list > /dev/null
    ENV MYSQL_MAJOR=8.0
    ENV MYSQL_VERSION=8.0.24-1debian10
    RUN echo 'deb http://repo.mysql.com/apt/debian/ buster mysql-8.0' > /etc/apt/sources.list.d/mysql.list
    RUN { \
            echo mysql-community-server mysql-community-server/data-dir select ''; \
        echo mysql-community-server mysql-community-server/root-pass password ''; \
        echo mysql-community-server mysql-community-server/re-root-pass password ''; \
        echo mysql-community-server mysql-community-server/remove-test-db select false; \
        } | debconf-set-selections \
        && apt-get update \
        && apt-get install -y \
            mysql-community-client="${MYSQL_VERSION}" \
            mysql-community-server-core="${MYSQL_VERSION}" \
        && rm -rf /var/lib/apt/lists/* \
        && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \
        && chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
        && chmod 1777 /var/run/mysqld /var/lib/mysql
    VOLUME [/var/lib/mysql]
    COPY dir:2e040acc386ebd23b8571951a51e6cb93647df091bc26159b8c757ef82b3fcda in /etc/mysql/
    COPY file:345a22fe55d3e6783a17075612415413487e7dba27fbf1000a67c7870364b739 in /usr/local/bin/
    RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
    ENTRYPOINT ["docker-entrypoint.sh"]
    EXPOSE 3306 33060
    CMD ["mysqld"]
    
  • reference

Xiao Han
  • 15
  • 3
  • This works the same way as the previous answer it's just a different image. Also oddly this shows the `FROM` part as the same image. The previous answer however doesn't even return FROM part. – xbmono Jul 07 '21 at 00:21
  • To get the accurate `basc-image tag`, you should build a Library for mapping images tag with files sha256 value. Because there is only a command associated with image tag that like `ADD file:f278386b0cef68136129f5f58c52445590a417b624d62bca158d4dc926c340df in /` in image history information – Xiao Han Dec 06 '21 at 07:03