Executando verificação de segurança...
3

Docker trabalhando com imagens parte II

Trabalhando com imagens

Comandos úteis do Dockerfile

FROM

Já vimos que o comando FROM serve para selecionarmos uma imagem base.

RUN

Também já vimos que o comando RUN nos permite fazer a execução de comandos no cotainer.

WORKDIR

O comando WORKDIR serve para definir qual será nosso diretório de trabalho dentro do container.

Sendo assim podemos editar nosso dockerfile da seguinte maneira:

COPY

O comando COPYserve para copiar um arquivo que está no meu computador para o container.

Sendo assim no mesmo diretório onde está o Dockerfile vamos criar um diretório novo chamado html e dentro dessa pasta vamos criar um arquivo chamado index.html com o seguinte conteúdo:

<h2>It Works !</h2>

E então vamos editar o Dockerfile da seguinte forma:

FROM nginx:latest

WORKDIR /app

RUN apt-get update && \
    apt-get install vim -y

COPY html/ /usr/share/nginx/html

Após editar faça um novo build

docker build -t jorgerabello/nginxcomvim:latest .

E execute o container

docker run -it --name nginx_server -p 8080:80 jorgerabello/nginxcomvim bash

root@0a6b398e44a8:/app# cat /usr/share/nginx/html/index.html
<h2>It Works !</h2>

root@0a6b398e44a8:/app#

Note que por padrão estamos no diretório /app por causa do WORKDIR, a segunda coisa é que temos o vim instalado, a terceira coisa é que foi feita a cópia do arquivo index.html, conforme se pode ver.

ENTRYPOINT e CMD

Todas as vezes em que executamos um container, é necessário executar algo para que o processo rode.

Vamos a um exemplo prático, crie um dockerfile com o seguinte conteúdo

FROM ubuntu:latest

CMD ["echo", "Hello World !"]
docker build -t jorgerabello/hello .

[+] Building 7.1s (6/6) FINISHED
....

E agora execute um container baseado nessa imagem

docker run --rm jorgerabello/hello

Hello World !

Veja que o CMD serviu para executar uma instrução ao rodar o nosso container.

No caso do nginx, provavelmente esse comando deve executar algo para deixar o servidor nginx rodando.

Porém o que o CMD vai executar pode ser substituído por meio de parâmetros, veja só:

docker run --rm jorgerabello/hello echo "mah oeeeeee"

mah oeeeeee

Veja que o comando de echo "Hello World !" foi substituído por echo "mah oeeeeee".

Por isso que quando você executa

docker run -it --rm jorgerabello/hello bash

root@087780a5067b:/#

Você cai num terminal, pois o CMD substitui seja lá o que for pelo parâmetro, nesse caso bash.

Mas e se eu não quiser que o comando seja substituído ? Daí utilizaremos o ENTRYPOINT.

Edite o dockerfile da seguinte forma:

FROM ubuntu:latest

ENTRYPOINT [ "echo", "Hello " ]

CMD ["World !"]

E faça um build

docker build -t jorgerabello/hello .

Agora execute:

docker run --rm jorgerabello/hello

Hello  World !
docker run --rm jorgerabello/hello Jorge

Hello  Jorge

Note que o docker substituiu apenas a parte do CMD, a do ENTRYPOINT ficou fixa.

O CMD sempre vai ser um comando variável que entrará como parâmetro do ENTRYPOINT.

ENTRYPOINT, CMD e EXEC

É possível ver como foi feito o dockerfile de uma imagem pronta.

Vamos tomar como base o nginx

Ao acessar uma imagem no docker hub e clicar sobre alguma tag, por exemplo na latest do nginx, você será redirecionado para o repositório onde está o Dockerfile dessa imagem.

No caso do nginx

#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#
FROM debian:bullseye-slim

LABEL maintainer="NGINX Docker Maintainers <[email protected]>"

ENV NGINX_VERSION   1.25.0
ENV NJS_VERSION     0.7.12
ENV PKG_RELEASE     1~bullseye

RUN set -x \
# create nginx user/group first, to be consistent throughout docker variants
    && addgroup --system --gid 101 nginx \
    && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \
    && apt-get update \
    && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \
    && \
    NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    NGINX_GPGKEY_PATH=/usr/share/keyrings/nginx-archive-keyring.gpg; \
    export GNUPGHOME="$(mktemp -d)"; \
    found=''; \
    for server in \
        hkp://keyserver.ubuntu.com:80 \
        pgp.mit.edu \
    ; do \
        echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
        gpg1 --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
    done; \
    test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
    gpg1 --export "$NGINX_GPGKEY" > "$NGINX_GPGKEY_PATH" ; \
    rm -rf "$GNUPGHOME"; \
    apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \
    && dpkgArch="$(dpkg --print-architecture)" \
    && nginxPackages=" \
        nginx=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \
    " \
    && case "$dpkgArch" in \
        amd64|arm64) \
# arches officialy built by upstream
            echo "deb [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bullseye nginx" >> /etc/apt/sources.list.d/nginx.list \
            && apt-get update \
            ;; \
        *) \
# we're on an architecture upstream doesn't officially build for
# let's build binaries from the published source packages
            echo "deb-src [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bullseye nginx" >> /etc/apt/sources.list.d/nginx.list \
            \
# new directory for storing sources and .deb files
            && tempDir="$(mktemp -d)" \
            && chmod 777 "$tempDir" \
# (777 to ensure APT's "_apt" user can access it too)
            \
# save list of currently-installed packages so build dependencies can be cleanly removed later
            && savedAptMark="$(apt-mark showmanual)" \
            \
# build .deb files from upstream's source packages (which are verified by apt-get)
            && apt-get update \
            && apt-get build-dep -y $nginxPackages \
            && ( \
                cd "$tempDir" \
                && DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" \
                    apt-get source --compile $nginxPackages \
            ) \
# we don't remove APT lists here because they get re-downloaded and removed later
            \
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
# (which is done after we install the built packages so we don't have to redownload any overlapping dependencies)
            && apt-mark showmanual | xargs apt-mark auto > /dev/null \
            && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
            \
# create a temporary local APT repo to install from (so that dependency resolution can be handled by APT, as it should be)
            && ls -lAFh "$tempDir" \
            && ( cd "$tempDir" && dpkg-scanpackages . > Packages ) \
            && grep '^Package: ' "$tempDir/Packages" \
            && echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list \
# work around the following APT issue by using "Acquire::GzipIndexes=false" (overriding "/etc/apt/apt.conf.d/docker-gzip-indexes")
#   Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
#   ...
#   E: Failed to fetch store:/var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages  Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
            && apt-get -o Acquire::GzipIndexes=false update \
            ;; \
    esac \
    \
    && apt-get install --no-install-recommends --no-install-suggests -y \
                        $nginxPackages \
                        gettext-base \
                        curl \
    && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \
    \
# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)
    && if [ -n "$tempDir" ]; then \
        apt-get purge -y --auto-remove \
        && rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; \
    fi \
# forward request and error logs to docker log collector
    && ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log \
# create a docker-entrypoint.d directory
    && mkdir /docker-entrypoint.d

COPY docker-entrypoint.sh /
COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d
ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 80

STOPSIGNAL SIGQUIT

CMD ["nginx", "-g", "daemon off;"]

Repare que o ENTRYPOINT é um shell script chamado
docker-entrypoint.sh

E seu conteúdo é esse aqui:

#!/bin/sh
# vim:sw=4:ts=4:et

set -e

entrypoint_log() {
    if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
        echo "$@"
    fi
}

if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then
    if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
        entrypoint_log "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"

        entrypoint_log "$0: Looking for shell scripts in /docker-entrypoint.d/"
        find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do
            case "$f" in
                *.envsh)
                    if [ -x "$f" ]; then
                        entrypoint_log "$0: Sourcing $f";
                        . "$f"
                    else
                        # warn on shell scripts without exec bit
                        entrypoint_log "$0: Ignoring $f, not executable";
                    fi
                    ;;
                *.sh)
                    if [ -x "$f" ]; then
                        entrypoint_log "$0: Launching $f";
                        "$f"
                    else
                        # warn on shell scripts without exec bit
                        entrypoint_log "$0: Ignoring $f, not executable";
                    fi
                    ;;
                *) entrypoint_log "$0: Ignoring $f";;
            esac
        done

        entrypoint_log "$0: Configuration complete; ready for start up"
    else
        entrypoint_log "$0: No files found in /docker-entrypoint.d/, skipping configuration"
    fi
fi

exec "$@"

Repare que o CMD do nginx está assim:

CMD ["nginx", "-g", "daemon off;"]

Ou seja os comandos necessários para subir o servidor nginx, porém todas as vezes em que executamos o container com exec e passamos bash por exemplo, esse comando que subiria o servidor é substituído pela execução de um shell bash.

EXPOSE

Note que no dockerfile também tem uma instrução chamada EXPOSE

EXPOSE 80

Essa instrução serve para expor a porta 80 do nosso container, porém isso não quer dizer que há um bind da porta 80 do container para a porta 80 da minha máquina, somente significa que a porta 80 do container vai ficar exposta, sendo assim, ao executar esse container será necessário utilizar o parâmetro -p para realizar o bind.

Exemplo Pŕatico

Para um exemplo prático vamos executar o seguinte

docker run --rm -it nginx bash

root@892ccb29d163:/# 

Veja que nesse caso aquele trecho do CMD do Dockerfile do nginx

CMD ["nginx", "-g", "daemon off;"]

Foi substituído pela execução do bash.

Agora se listarmos o conteúdo do diretório em que estamos, podemos ver que aquele shell script referenciado no ENTRYPOINT existe de fato no container, veja:

root@892ccb29d163:/# ls

bin   dev		   docker-entrypoint.sh  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc			 lib   lib64  media   opt  root  sbin  sys  usr
ENTRYPOINT ["/docker-entrypoint.sh"]

Sendo assim ao executar essa imagem do nginx o que acontece é a execução do shell script docker-entrypoint.sh recebendo os parâmetros nginx -g daemon off;, é mais ou menos como se você executasse o seguinte comando:

./docker-entrypoint.sh nginx -g daemon off;

Se você prestou atenção ou inspecionou o shell script docker-entrypoint.sh, notou que no final dele tem uma instrução, exatamente assim:

...
exec "$@"

Sempre que um shell script tem essa instrução significa que o shell script deverá aceitar os parâmetros passados depois da execução do shell script.

Vamos testar isso ! No shell do container execute o script assim:

root@892ccb29d163:/# ./docker-entrypoint.sh echo "HELLO"
HELLO

root@892ccb29d163:/# 

Veja que o parâmetro passado foi aceito, como era de se esperar.

Publicando uma imagem no DockerHub

Para esse exemplo vamos customizar uma imagem do nginx

Para isso crie um diretório novo, e dentro dele crie um Dockerfile com o seguinte conteúdo:

FROM nginx:latest

COPY html /usr/share/nginx/html

ENTRYPOINT ["/docker-entrypoint.sh"]

CMD ["nginx", "-g", "daemon off;"]

Note que estamos usando a imagem base do nginx, e tmbm seu entrypoint e cmd.

Porém antes do entrypoint, estamos fazendo a cópia de um diretório chamado html.

Sendo assim vamos criar o diretório html e dentro dele vamos criar um arquivo index.html com o seguinte conteúdo:

<h1>It Works !</h1>

Agora vamos fazer o build dessa imagem:

docker build -t jorgerabello/cnginx .   

Repare que a imagem foi construída:

❯ docker images         
REPOSITORY            TAG       IMAGE ID       CREATED              SIZE
jorgerabello/cnginx   latest    b1824e4c6baa   About a minute ago   187MB

Agora vamos executar essa imagem em um container:

docker run --rm -d -p 8080:80 jorgerabello/cnginx 

38b16e123dee14454a3e3edf3b85b8cb284a6cdf39509fe7ac8d54c917b83091
docker ps

CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                   NAMES
38b16e123dee   jorgerabello/cnginx   "/docker-entrypoint.…"   18 seconds ago   Up 18 seconds   0.0.0.0:8080->80/tcp, :::8080->80/tcp   sharp_rosalind

E se acessarmos o container num navegador ou via curl, agora temos uma imagem personalizada do nginx:

curl localhost:8080

<h1>It Works !</h1>

Agora, se você ainda não tiver crie uma conta no DockerHub.

Note que, na tag da minha imagem eu usei jorgerabellodev/nome_da_imagem, isso por que esse é meu usuário no dockerhub, no seu caso você deve utilizar o seu usuário.

O próximo passo é fazer login no docker hub, pra isso digite no seu terminal:

docker logout
docker login

Entre com seu usuário e senha do Docker Hub

Agora basta executar um docker push da seguinte forma:

docker push jorgerabello/cnginx:latest 
The push refers to repository [docker.io/jorgerabello/cnginx]
5e4e8fc78137: Pushed 
9e96226c58e7: Mounted from library/nginx 
12a568acc014: Mounted from library/nginx 
7757099e19d2: Mounted from library/nginx 
bf8b62fb2f13: Mounted from library/nginx 
4ca29ffc4a01: Mounted from library/nginx 
a83110139647: Mounted from library/nginx 
ac4d164fef90: Mounted from library/nginx 
latest: digest: sha256:89388bd2bd41c7354b1e173aec95daea06c1a47dc89c661b2e553ab46df8cad5 size: 1985

Ao terminar o push você poderá verificar a imagem no seu docker hub, no meu caso ficou nesse endereço.

Carregando publicação patrocinada...