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!
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
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.