Habitos que desenvolvedores precisam ter quando estão trabalhando com Docker

Se você dev, assim como eu, gosta de utilizar o Docker para colocar tudo o que há de bom na sua aplicação, seja ela um front com react ou um monolito de 300 dependências, então esse blog é para você.

Eu preparei alguns tópicos com boas práticas que você devia seguir, e vou incluir alguns linguagens populares seguido de dicas que podem ajudar você na sua produtividade e também na sua organização com os containers.

Para que isso não fique repetitivo, irei pelo menos usar dois exemplos para tópico (isso se tiver necessidade claro, não queremos repetir o óbvio toda a hora).

Mas Drack, tem umas coisas que eu discordo nesse post, o que eu faço?

Comentarios? Só não vai escrever uma redação do enem ein

Tenho algumas dicas em mentes, posso escrever também?

insira aqui a mesma resposta que eu utilizei para a outra pergunta

Vocês estão prontas crianças? Pois a nossa aventura nesse oceano começa agora :dolphin:

🗂️ Dockerfile próprias

"Ué, mas eu já faço isso na minha aplicação Drack, não entendi esse tópico"

Meu querido amigo(a), quando estamos trabalhando com Docker, principalmente docker-compose, onde queremos orquestrar nossas imagens e dividir isso em um container isolado, a gente acaba seguindo um padrão onde fazemos tudo dentro do nosso docker-compose.yml

Nós acabamos por puxar a imagem diretamente do arquivo .yml sem realizar nenhum comando específico ou configuração que a gente quer.

"Ah mas pra isso a gente poderia usar o command do docker-compose Drack"

Mas e se eu precisar executar mais de um comando, se eu quero uma workdir personalizada? E se eu precisar alterar configurações de usuário? Vou fazer isso tudo dentro do command? Claro que não ❌

# docker-compose.yml

services:

  db:
    image: postgres:14
    environment:
      POSTGRES_PASSWORD: senha_secreta
      POSTGRES_USER: drack_o_agente
      PG_DATA: /var/lib/postgresql/data
    ports:
      - 5432:5432

Aqui por exemplo, a gente apenas importa a imagem do postgres sem passar nenhuma configuração a mais, e se eu precisar de 2 banco de dados com configurações complexas? Como irei passar tudo isso num compose?

Não se preocupe meu caro, estou aqui eu para te apresentar a maneira mais delicinha para resolver esse problema, basta você criar uma Dockerfile para ela e prontinho, você pode mexer à vontade na sua instância do postgres. E olha como a gente tem um compose mais bonitinho para a gente usar.

Eu irei criar uma pasta chamada .docker e dentro dessa pasta criar uma outra pasta chamada postgres, e dentro dela irá ter o meu Dockerfile do postgres.

# .docker/postgres/Dockerfile

FROM postgres:14
# docker-compose.yml

services:

  db:
    build: .docker/postgres
    environment:
      POSTGRES_PASSWORD: senha_secreta
      POSTGRES_USER: drack_o_agente
      PG_DATA: /var/lib/postgresql/data
    ports:
      - 5432:5432

"Ah Drack mas é só isso? Mas que perca de tempo ein"

Calma, calma, se você acha isso aqui é pequena coisa, então olha só o que a gente consegue fazer seguindo esse padrão.

Se você quiser retirar permissões de ROOT para seus volumes e não perder nadinha dos seus dados super secretos, então olha como você pode configurar isso de maneira simples usando Dockerfiles

FROM postgres:14

RUN usermod -u 1000 postgres

Você quer criar um arquivo de multi stage e passar uma porta personalizada para seu container sem perder os seus dados e ainda mantendo o docker-compose limpinho? Você pode agora.

FROM postgres:14 as development

RUN usermod -u 1000 postgres

CMD ["-p", "5433"]

FROM postgres:14 as test

RUN usermod -u 1000 postgres

👷 Multi-stage build

Você cria containers isolados para desenvolvimento correto? Mas você cria eles para desenvolvimento e produção? Não?! Como assim não cara?

Multi Stage build foi lançado na versão 17.05 e permite que um build possa ser reutilizado em diversas etapas da geração da imagem, deixando os Dockerfiles mais fáceis de ler e manter.

Então vamos supor que eu tivesse trabalhando em uma aplicação golang, nós sabemos que no final essa aplicação golang ela irá ser um único arquivo binário, e que as únicas deps que precisamos são o go.mod e go.sum justamente para instalar elas e fazer nosso app rodar. Mas e se eu conseguisse pegar somente esses 3 arquivos de um outro container, mais precisamente do meu container de desenvolvimento? Ia ser uma maravilha, não é?

Mas é claro que a gente pode fazer isso, vem comigo!

Para explicar de forma simples, irei fazer um Dockerfile com multi-stage e um docker-compose de dev e de produção, e irei comentar o conteudo dentro do Dockerfile.

# O nome do nosso primeiro stage irá se chamar builder, 
justamente por ele ser o principal target que irá fazer 
todo o processo de setup da nossa aplicação go e buildar ela no final.

FROM golang:1.18.8-alpine as builder

ENV GO111MODULE=on

# apk para a gente adicionar dependências como bash, curl e afins
RUN apk update && apk add --no-cache bash

# Workdir da nossa aplicação
WORKDIR /go/src/app

# Live Reload para golang
RUN go install github.com/cosmtrek/air@latest

# Copiar os arquivos de dependências do projeto golang
COPY go.mod /go/src/app/
COPY go.sum /go/src/app/

# Instalar essas dependências
RUN go mod download
RUN go mod tidy

# Copiar todo o nosso projeto
COPY . /go/src/app/

# Expor as portas do projeto
EXPOSE 8080
EXPOSE 9090
EXPOSE 5454

# E por fim fazer a build da nossa aplicação
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# E aqui, será o stage que não vai ter nome,
mas sinta-se a vontade para chamar ele do que quiser, 
por enquanto, a gente importa o alpine que é um sistema linux 
tão pequeno e completo ao mesmo tempo que é perfeito para executar nossa aplicação go.

FROM alpine:latest

# Rodar o APK para a gente conseguir certificados de execução
RUN apk --no-cache add ca-certificates

# Workdir da nossa aplicação
WORKDIR /go/src/app

#  Variável do GIN (micro framework go)
ENV GIN_MODE release

# E por fim, o COPY, o COPY ele permite a gente poder 
copiar arquivos de outros containers e jogar no ambiente 
atual que estamos rodando, aqui por exemplo, estou 
pegando tanto a aplicação GO que foi compilada, 
o meu app.env e minha pasta de migrations, percebe 
o quão pequeno irá ficar nosso container sem ter 
todos os arquivos do nosso projeto? Aqui, a gente tem somente o necessario.

COPY --from=builder /go/src/app/main /go/src/app/
COPY --from=builder /go/src/app/app.env /go/src/app/
COPY db/migrations /go/src/app/db/migrations/

EXPOSE 8080
EXPOSE 9090
EXPOSE 5454

# E no final, iremos executa-lo :D
CMD ["./main"]

Agora vamos dar uma olhada nos nossos docker-compose de desenvolvimento e produção.

docker-compose de desenvolvimento

# docker-compose.yaml
version: "3"

services:
  app:
    build:
      target: builder
      context: .
    command: air .
    ports:
      - 8080:8080
      - 9090:9090
      - 5454:5454
    volumes:
      - .:/go/src/app

docker-compose de produção

# docker-compose.yaml
version: "3"

services:
  app:
    build: .
    ports:
      - 8080:8080
      - 9090:9090
      - 5454:5454
    volumes:
      - .:/go/src/app

Volumes locais 🥤

Sabe aquela instancia do postgres que usei de exemplo mais cedo?

Eu justamente falei sobre os dados que você poderia ter salvo sem tomar aquele erro chato de acesso de root quando fosse trabalhar com desenvolvimento, mas como que eu posso deixar esses dados salvos localmente em vez de criar um volume? Ora ora isso é muito simples, vem comigo.

# docker-compose.yml

services:

  db:
    build: .docker/postgres
    environment:
      POSTGRES_PASSWORD: senha_secreta
      POSTGRES_USER: drack_o_agente
   volumes:
      - .docker/dbdata:/var/lib/postgresql/data
    ports:
      - 5432:5432

Aqui, eu tou passando que meus dados do container do postgres, que estão armazenados em /var/lib/postgresql/data, irão também ficar salvos de maneira linkada em .docker/dbdata, e olha que interessante, se você usar a Dockerfile que passei antes, você pode explorar toda o data do postgres como se fosse um usuário sudo em liberdade!

FROM postgres:14

RUN usermod -u 1000 postgres

✨ Alpine forever

Isso aqui é mais como um bônus, mas de preferência, se a imagem que você procura tiver uma variante com o alpine linux, você vai estar literalmente tendo o 2 em 1 com mais bônus do que consegue imaginar, além de uma imagem super compacta e pequena, você ainda pode usar um linux com recursos imensos para trabalhar de maneira como quiser, em comparação, você vai conseguir rodar os seus comandos tão rápido quanto você rodaria e iria esperar quase minutos num debian.

🔏 Envs

Eu gosto de seguir um certo padrão de valores para envs, tanto no docker-compose quanto em minhas aplicações, vamos usar o NodeJS, se eu tivesse uma aplicação com o Prisma ORM e precisasse importar todas as envs nele, eu particularmente acho mais prático deixar tudo isso em um arquivo .env e jogar ele pro docker-compose.yaml, desse jeito, a gente reduz as linhas desnecessárias e ainda podemos organizar os .envs em um lugar só.

#.env

# NestJS
SERVER_PORT=3000

# Postgres
DB_USERNAME=drack_o_agente
DB_PASSWORD=123
DB_NAME=graphql

DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@localhost:5432/${DB_NAME}?schema=public"
# docker-compose.yml

version: '3'

services:
  app:
    container_name: nestjs-app-development
    build:
      context: .
      target: development
    volumes:
      - .:/home/node/app
      - ./node_modules:/home/node/app/node_modules
    ports:
      - ${SERVER_PORT}:${SERVER_PORT}
    env_file:
      - .env

🎉 Finalização

Então por hoje é só, eu sei que posso ter escrito muita coisa mas pode ter certeza que além da produtividade que você irá conquistar, você consegue trabalhar com mais riqueza e classe no Docker além de um desempenho nas suas versões finais de produção.

Um abraço, e até a próxima!

Cara, muito legal ver cada uma dessas dicas! Muito importante pra quem lida com docker todo dia e quer ter uma experiência bacana de desenvolvimento.

Meus parabéns pelo conteúdo, ficou muito legal 🎉👏🏻

Eu que agradeço o carinho de você ter lido o meu post :)

Valeu Drack, em breve eu vou montar a parte de infra do meu sistema The Seed, e já estava planejando usar docker para começar <3

Cara delicinha essas dicas, vou passar a usar.. muito interessante, parabéns pelo contéudo!

Eu que agradeço de você poder ter lido meu primeiro post, quem sabe eu poste mais algumas dicas para os devs :^)

Incrível, cara! Eu trabalho com muitos microsserviços e docker e isso tudo que você disse vai me ajudar bastante a dar uma organizada aqui, gostei muito mesmo. Valeu!

é perfeito pra quem precisa levantar uma pequena cesta de aplicações xD

Muito bom o conteúdo brother, sou Analista DevOps e sempre estou auxiliando os devs no processo de conteinerização da aplicação. Muitos gostam do docker-compose mas não gostam de usar o dockerfile.

Vale lembrar que falando em um ambiente kubernetes, é essencial ter o dockerfile bem otimizado, para uma boa utilização de recursos e escalabilidade

Muito legal compartilhar tua experiência, entrei no tab news após seu post, contéudo muito relevante e com peso profissional. Parabéns.

Obrigado pelo conteúdo, recentemente eu estava aprendendo docker, e os topicos que você citou era especificamente aonde eu estava travado ^^

Eu espero ter ajudado nos seus estudos! :D