Dockerfile - Principais comandos para criar a receita da imagem

Docker 21 de Ago de 2020

Vamos aprender os principais comandos utilizados no Dockerfile.

Introdução

Dockerfile, como vimos neste post, é responsável por criar nossas imagens. Podemos dizer que ele é a receita que essa imagem vai seguir. É com esse arquivo que podemos gerar o build e criar o container a partir dele.

Oferta especial NLW | Rocketseat
Do zero ao avançado, tenha acesso imediato a todas as formações da Rocketseat em uma única plataforma para você aprender na prática as tecnologias mais demandadas do mercado.

Para criar um Dockerfile é simples, basta criar um arquivo com o nome Dockerfile.

Para gerar a imagem a partir do Dockerfile, executamos o comando abaixo no mesmo local que o arquivo se encontra:

docker build -t nome_da_imagem .

Para criar o container a partir dessa imagem construída, podemos executar o comando:

docker run nome_da_imagem

FROM

Instrução obrigatória que indica qual imagem vai ser utilizada como ponto de partida. Podemos  criar uma imagem a partir de uma imagem do Node.js ou um sistema Linux usando uma imagem do Ubuntu e assim por diante. É possível criar sua própria imagem usando a instrução FROM scratch.

FROM node:12-alpine

O comando acima indica que será criado uma imagem com Node.js.

RUN

Serve para executar comandos no processo de montagem da imagem que estamos construindo no Dockerfile, ele é executado durante o build (construção da imagem) e não durante a construção do container. Em um Dockerfile é possível ter mais de um comando RUN.

FROM node:alpine

WORKDIR /usr/app

RUN npm init -y
RUN npm install cowsay
RUN npx cowsay Hello Docker

RUN cat package.json

Quando rodamos o comando abaixo, podemos ver o log de cada RUN sendo executado no processo de criação da imagem:

docker build -t nodej/nodej .

Resultado:

❯ docker build -t nodej/nodej .
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM node:alpine
 ---> 0f2c18cef5d3
Step 2/6 : WORKDIR /usr/app
 ---> Using cache
 ---> 0399f14efe3b
Step 3/6 : **RUN npm init -y**
 ---> Using cache
 ---> da2345d90786
Step 4/6 : **RUN npm install cowsay**
 ---> Using cache
 ---> bf6c1a19a89a
Step 5/6 : **RUN npx cowsay Hello Rocketseat**
 ---> Running in 78f5486fa633
 __________________
< Hello Rocketseat >
 ------------------
        \\   ^__^
         \\  (oo)\\_______
            (__)\\       )\\/\\
                ||----w |
                ||     ||
Removing intermediate container 78f5486fa633
 ---> 02d9013dbf0b
Step 6/6 : **RUN cat package.json**
 ---> Running in 675e768b4b16
{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cowsay": "^1.4.0"
  }
}
Removing intermediate container 675e768b4b16
 ---> 260e87152b92
Successfully built 260e87152b92
Successfully tagged nodej/nodej:latest

CMD

Diferente do RUN, o CMD executa apenas na criação do container e não no build da imagem. Deve ser único no Dockerfile.

FROM node:alpine

WORKDIR /usr/app

RUN npm init -y
RUN npm install cowsay
RUN npx cowsay Hello Rocketseat

RUN cat package.json

CMD npm start

Execute o comando abaixo para recriar a imagem:

docker build -t nodej/nodej .

Depois rode o comando abaixo para criar o container:

docker run -t nodej/nodej

O comando vai criar a imagem e dentro do container será executado o npm start, porém no package.json não foi definido no script start, mas perceba que o CMD executou apenas no container e não na construção da imagem. E o log do erro só apareceu quando criamos o container.

> docker run -t nodej/nodej   
npm ERR! missing script: start
npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2020-08-20T17_40_32_268Z-debug.log

EXPOSE

Instrução que informa qual porta deverá ser liberada. EXPOSE não é um comando que libera a porta, ele serve para ficar documentado no Dockerfile quais portas deverão ser liberadas ao criar o container.

Para efetivamente liberar a porta é preciso passar o parâmetro -p  no comando docker run, vamos ver a seguir.

FROM node:alpine

WORKDIR /usr/app

RUN npm init -y
RUN npm install cowsay
RUN npx cowsay Hello Rocketseat

RUN cat package.json

EXPOSE 3000 ## informa que deverá ser liberado a porta 3000 na criação do container

CMD npm start

Execute o comando abaixo para criar o container e liberar a porta 3000:

docker run -p 3000:3000 -d nodej/nodej

Para liberar a porta usamos o parâmetro -p 3000:3000. Está sendo realizado também o redirecionamento de portas, onde o primeiro parâmetro 3000 é a porta do host e o segundo :3000 é a porta do  container. Isto é, toda vez que o host receber uma requisição na porta 3000, o container irá ouvir a requisição na porta 3000 também.

Liberando portas na aplicação: Porta 3000 Host e :3000 Container.

Podemos liberar portas TCP e UDP, por padrão, quando não é especificado /tcp ou /udp, é liberado a porta TCP.

Oferta especial NLW | Rocketseat
Do zero ao avançado, tenha acesso imediato a todas as formações da Rocketseat em uma única plataforma para você aprender na prática as tecnologias mais demandadas do mercado.
docker run -p 80:80/tcp -p 80:80/udp ...

Por padrão, a porta tcp é liberada quando não é especificado o protocolo, exemplo:

docker run -p 8080:80

COPY e ADD

Comandos para copiar arquivos e pastas de um lugar específico na máquina local para uma pasta no container.

Diferença é que ADD copia arquivos remotos para alguma pasta na imagem e também pode copiar arquivos compactados (tar.gz) que serão descompactados na imagem automaticamente.

Por questões de semântica, sempre utilize COPY para copiar arquivos e pastas locais e ADD para arquivos remotos ou compactados. Utilize o ADD se realmente for necessário. Leia as boas práticas sobre COPY e ADD.

Exemplos:

Se quiser acompanhar em sua máquina, faça os seguintes procedimentos abaixo:

  • COPY

Digite no terminal dentro de um diretório para criar um projeto com Node.js:

yarn init -y

Altere o package.json:

{
  "name": "docker-node",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "MIT"
}

Crie o arquivo index.js na raiz do projeto com o seguinte conteúdo:

console.log("Qual é a soma de 2 + 2: ", 4);

Crie o arquivo Dockerfile na raiz do projeto:

FROM node:alpine

WORKDIR /usr/app

COPY package.json ./
COPY index.js ./

RUN npm install 

RUN cat package.json

RUN npm start

EXPOSE 3000

# CMD npm start

Execute o comando abaixo no terminal e observe o log:

docker build -t nodej/nodej .

No final será impresso:

Qual é a soma de 2 + 2 = 4
  • ADD

Exemplo com o comando ADD:

FROM ubuntu:18.04

RUN mkdir /myfolder

WORKDIR /myfolder

ADD /sample.tar.gz ./

RUN ls  ## listando arquivos

RUN ls project

RUN ls -la

RUN echo "FIM"

Execute o comando abaixo e observe o log:

docker build -t nodej/nodej .

Será adicionado um arquivo compactado da máquina do host para o container recém criado dentro da pasta myfolder, em seguida o arquivo será descompactado automaticamente.

VOLUME

Quando adicionamos VOLUME no Dockerfile estamos informando um ponto de montagem, criando uma pasta que ficará disponível entre o container e o host.

FROM ubuntu:18.04

RUN mkdir /minha_pasta
RUN echo "hello world" > /minha_pasta/hello.txt

VOLUME /minha_pasta

Dockerfile acima define a utilização da imagem do ubuntu:18.04, cria uma pasta chamada minha_pasta e depois cria um arquivo hello.txt com o conteúdo "hello world" dentro da pasta, por fim é disponibilizado o VOLUME /minha_pasta para que o host tenha acesso.

Podemos fazer espalhamento do Volume conforme podemos ver neste post.

WORKDIR

Define uma pasta dentro do container onde os comandos serão executados.

FROM node:alpine

WORKDIR /usr/app

COPY package.json ./

RUN npm install

CMD npm start

Estamos definindo que na pasta usr/app serão executados os comandos seguintes, o arquivo package.json será copiado do host para user/app, inclusive o comando RUN npm install será executado dentro do contexto definido no WORKDIR.

USER

Podemos alterar os usuários para executar os comandos. No exemplo abaixo defini o usuário node para executar os comandos e, como não precisa de privilégios de super usuário (root), podemos usar usuário sem tantos privilégios.

FROM node:alpine

USER node ## definindo o usuário que realizar os comandos

RUN whoami ## qual usuário está usando o terminal, será impresso node

WORKDIR /usr/app

COPY package.json ./

RUN npm install

CMD npm start

Conclusão

Interessante observar a flexibilidade que temos quando criamos nossas imagens e containers com Docker.

Vimos os principais comandos: FROM para definir uma imagem; RUN para executar algum comando no processo de build da imagem; CMD para executar algum comando após a criação do container; EXPOSE para documentar qual porta deve ser liberada na criação do container; COPY e ADD para copiar arquivos locais e remotos para o container; VOLUME para definir um local onde serão armazenados arquivos que serão compartilhados com o host; WORKDIR para definir um ponto de partida de onde os arquivos serão executados; e, por fim, o comando USER para definir qual usuário irá executar os comandos no container.

No canal no youtube temos um vídeo sobre Deploy de aplicações NodeJS à AWS com Docker, que traz alguns conceitos mais avançados sobre essa ferramenta, assista:

E aí o que achou do post?

Espero que tenha curtido! 💜

O aprendizado é contínuo e sempre haverá um próximo nível! 🚀

Marcadores

Thiago Marinho

Dev Apaixonado por Tecnologia & Educação! Evolua rápido como a tecnologia, aprendizado é contínuo e sempre haverá um próximo nível. Boost Yourself! 🚀