O Dockerfile que o Rails te deu
O rails new escreve um Dockerfile. A maioria dos desenvolvedores nunca abre esse arquivo, faz deploy com Kamal, e tudo funciona. É um bom desfecho e também uma oportunidade perdida: o arquivo é uma aula compacta sobre containers em produção, e já está no seu repositório.
Este app tem um. As primeiras linhas dizem para que ele serve:
# syntax=docker/dockerfile:1
# check=error=true
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t newbie_on_rails .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name newbie_on_rails newbie_on_rails
Antes de tudo vêm duas diretivas. # syntax=docker/dockerfile:1 escolhe o frontend atual de Dockerfile em vez do que vier com o daemon, e é isso que libera os recursos modernos da aula 4. # check=error=true transforma avisos de lint da build em falhas: um erro interrompe a build em vez de passar despercebido no meio do log.
O vocabulário, num parágrafo
Uma imagem é um sistema de arquivos mais os metadados que dizem como iniciar um processo. É um artefato de build: imutável e identificado por um digest. Um container é uma imagem em execução. Uma camada é o diff de sistema de arquivos que uma instrução de build produz. Imagens são pilhas de camadas, que podem ser compartilhadas entre imagens e reaproveitadas em cache entre builds.
As aulas 2 e 4 cabem nessas duas últimas frases. Quase toda otimização de Dockerfile é, no fundo, uma decisão sobre camadas.
Lendo a build deste app
O arquivo tem três linhas FROM, o que significa três estágios:
ARG RUBY_VERSION=4.0.1
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
...
FROM base AS build
...
FROM base
O estágio base instala o que o app precisa para rodar (curl, libjemalloc2, libvips, postgresql-client) e define as variáveis de ambiente de produção.
O estágio build acrescenta o que é preciso para compilar: build-essential, git, libpq-dev, libyaml-dev, pkg-config. Ele instala as gems, copia o app e precompila os assets.
O estágio final parte de base de novo, e não de build, e copia apenas os artefatos prontos:
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
Um compilador C, o cache de gems e o ferramental de build nunca chegam à imagem entregue. Isso é uma build multi-estágio, e a aula 3 mostra por que ela importa tanto.
Escolhas que merecem atenção
Quatro linhas desse arquivo guardam decisões que, sem elas, você só aprenderia apanhando.
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
BUNDLE_DEPLOYMENT="1" faz o bundler se recusar a atualizar o Gemfile.lock. A imagem sai do lockfile que você commitou, ou a build falha. O bundler nunca resolve outra coisa em silêncio.
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
A precompilação de assets inicializa o app, e a inicialização normalmente exige um secret. SECRET_KEY_BASE_DUMMY=1 fornece um secret descartável, então a master key real nunca é necessária durante a build. Ela nunca aparece num argumento de build, numa camada ou num log de CI.
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
O container roda como usuário não root, e só os quatro diretórios que precisam ser graváveis são entregues a ele. Todo o resto fica somente leitura para o processo do app.
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
O comando padrão sobe o servidor atrás do Thruster, que está no Gemfile deste app. O Thruster fica na frente do Puma e cuida de cache HTTP, compressão e aceleração X-Sendfile.
Para que serve este curso
O arquivo gerado é bom. Mesmo assim, um dia você vai precisar mudá-lo: um pacote de sistema que uma gem exige, um passo de build para algo que o Rails não conhece, outra imagem base, uma build de CI mais rápida. Quem mexe num Dockerfile sem entendê-lo transforma uma edição de cinco minutos numa imagem de dois gigabytes com a master key gravada na camada quatro.
- O Dockerfile que o Rails te deu (você está aqui).
- Camadas e o cache de build.
- Builds multi-estágio, e o que é descartado.
- Deixando as builds rápidas.
- Deixando as imagens pequenas e seguras.
- O que acontece em tempo de execução.
- Juntando tudo: um Dockerfile customizado.
Abra o Dockerfile deste repositório enquanto lê. Todo exemplo deste curso é aquele arquivo.
Pontos-chave
- O
rails newgera um Dockerfile de produção; ele é uma aula compacta sobre builds de container, no seu repositório. - Uma imagem é uma pilha de camadas, um container é uma instância em execução, e uma camada é o diff de uma instrução. Quase toda otimização é sobre camadas.
- O arquivo deste app tem três estágios:
basepara pacotes de runtime,buildpara compilação, e um final que copia só os artefatos prontos sobre obase. BUNDLE_DEPLOYMENT=1constrói estritamente a partir do lockfile commitado, eSECRET_KEY_BASE_DUMMY=1mantém a master key real inteiramente fora da build.- A imagem roda como usuário não root, com apenas
db,log,storageetmpgraváveis.
Continue com a aula 2
Camadas e o cache de build
Como cada instrução vira uma camada em cache, o que a invalida e por que a ordem importa.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.