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 COPY
serve 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.