Docker - Usando contêineres para criar ambientes reproduzíveis

SECOMP 2025

Brenno Lemos

UFSJ | Secomp 2025

0. Sobre mim

Sou Mestre em Ciência da Computação pela UFSJ.

Trabalho como Engenheiro de Software há 5 anos.

Mais recentemente, trabalho como Engenheiro de Software Sênior Rust para a Arionkoder.

Na SECOMP 2022, participei como palestrante e apresentei a linguagem Rust.

Na SECOMP 2023, ofertei um minicurso de Rust com o foco de ensinar todo o básico até funcionalidades avançadas.

Minicurso Docker
UFSJ | Secomp 2025

Antes de começarmos

Cheque se sua máquina está com Docker instalado e com as permissões corretas

$ docker run --rm hello-world
Minicurso Docker
UFSJ | Secomp 2025

Aprenda a usar Docker!

Minicurso Docker

1. O que é Docker?

Docker é uma coleção de ferramentas de virtualização. Pode ser comparado com máquinas virtuais, porém possui uma abordagem programática.

Isto quer dizer que o Docker é ideal para a criação de ambientes reproduzíveis, isto é, ambientes que possam ser criados uma vez e executados inúmeras vezes em diferentes máquinas, sempre gerando o mesmo resultado.

Minicurso Docker
UFSJ | Secomp 2025

1.1. Pra que serve o Docker?

Docker possui inúmeras aplicações. Aqui estão algumas que abordaremos neste minicurso:

  • Rodar programas sem a necessidade de instalá-los na máquina;
  • Compilar programas para outros sistemas operacionais;
  • Executar serviços como webservers e bancos de dados;
  • Rodar pipelines em ambientes externos como GitHub Actions;
Minicurso Docker

2. Rodando programas sem instalá-los

Neste exemplo, executaremos a aplicação Copyparty usando Docker, sem a necessidade de instalar o programa ou suas dependências no computador.

docker run --rm -it \
 -u 1000 -p 3923:3923 \
 -v .:/w copyparty/min -v .::rw:a

Rode este comando e acesse o serviço pelo navegador:

http://localhost:3923

Todos os comandos usados durante este minicurso estão disponíveis no repositório

2.1. Intermissão: A anatomia de um comando Docker

  • docker run
  • --rm
  • -it
  • -u 1000
  • -p 3923:3923
  • -v .:/w
  • copyparty/min
  • -v .::rw:a
  • Comando e subcomando. "Execute algo"
  • Remova o contêiner após ele terminar de executar
  • -i= interativo; -t = TTY, ou "terminal"
  • Executar como usuário 1000 (o padrão)
  • Exponha a porta 3923
  • Compartilhe a pasta local com o contêiner em /w
  • Nome da imagem (em https://hub.docker.com/)
  • Este é um argumento para o programa executado no contêiner. Não tem relação com o -v anterior
Minicurso Docker
UFSJ | Secomp 2025

2.2. Usando qualquer versão de Python

Muitos programas e ferramentas famosas estão disponíveis em formato de imagens no Docker Hub.

Por exemplo, é possível usar a versão mais recente de Python para rodar um script:

docker run --rm -v ./:/tmp python:3.13 python3 /tmp/hello_world.py

Isto pode ser útil quando a versão desejada do interpretador não estiver disponível na máquina. Por exemplo, Ubuntu 22.04 e derivados possuem Python 3.10 instalado, mas a versão 3.13 já foi lançada.

Minicurso Docker
UFSJ | Secomp 2025

2.2.1. E as dependências?

Poderíamos escrever um shell script para instalar uma biblioteca e depois executar o nosso script.

mult_array.sh

#!/usr/bin/env bash
pip3 install numpy
python3 mult_array.py

mult_array.py

import numpy as np
x = np.array([1, 2, 3, 4])
print(x * 3)
docker run --rm -v ./:/tmp -w /tmp python:3.13 bash mult_array.sh

Contudo, há um problema: sempre que executamos o contêiner, a dependência tem que ser baixada novamente. Veremos no próximo capítulo como podemos construir novas imagens de Docker para evitar isso.

Minicurso Docker

3. Compilando programas para outro 'OS'

É possível usar o Docker para compilar programas para outros sistemas operacionais. Por exemplo, seria possível rodar Docker no Windows ou MacOS (internamente gerenciado por VMs) e compilar programas para Linux.

No próximo exemplo, escreveremos uma nova imagem de Docker que compila um programa em C e o executa.

Minicurso Docker
UFSJ | Secomp 2025

3.1. Criando sua primeira imagem

FROM debian
RUN apt-get update && apt-get install -y gcc
COPY main.c main.c
RUN gcc main.c -o main
ENTRYPOINT ["./main"]
<-- Camada base
<-- Instale GCC
<-- Copie o programa local para o contêiner
<-- Compile o programa
<-- Rode o programa
$ docker build . -t minha_imagem -f Dockerfile

$ docker run --rm minha_imagem
main.c pode ser encontrada no repositório, em exercises/chap-3/main.c

3.2. Intermissão: O que é uma imagem de Docker?

De forma simplificada, todo comando executado com Docker está dentro de um contêiner. Todo contêiner é baseado em uma imagem, e toda imagem possui múltiplas camadas. Nada se cria, tudo se copia.

Cada camada pode modificar o sistema de arquivos. Por exemplo, se a camada base for uma distribuição Linux como o Debian, uma possível próxima camada poderia instalar um programa, como Emacs, GCC, ou Python.

As explicações neste slide estão dramaticamente simplificadas para efeitos didáticos.
UFSJ | Secomp 2025

3.2.1. Overlay FS

A modificação de arquivos por camada ocorre de uma maneira que evite o desperdício de armazenamento da máquina. Quando um arquivo é modificado dentro de um contêiner em execução, por exemplo, esta modificação ocorre apenas na camada do contêiner, que é sobreposta sobre as camadas anteriores. Isto é gerenciado por um sistema do Linux conhecido como OverlayFS¹.

¹ Overlay FS é uma das maneiras com a qual o Docker pode escolher gerenciar as camadas, mas todas as outras opções funcionam de maneiras similares para o propósito desta explicação.
UFSJ | Secomp 2025

3.3. Reparação histórica: criando nossa imagem de Python

FROM python:3.13-alpine
RUN pip install numpy
WORKDIR /app
COPY mult_array.py .
ENTRYPOINT ["python3", "mult_array.py"]
$ docker build . -t python_mult_array -f Dockerfile

$ docker run --rm python_mult_array

Nesta imagem, o NumPy será instalado durante o comando docker build. Portanto, não será necessário reinstalá-lo cada vez que executarmos a imagem com docker run

mult_array.py pode ser encontrada no repositório, em exercises/chap-2/mult_array.py

4. Executando serviços com Docker

Nas seções anteriores, foi demonstrado como o Docker pode ser usado pra criar um ambiente reproduzível, seja para executar um programa que finaliza após algum tempo (como em 3.1.) ou para agir como um servidor, que executa até que o usuário o termine (como em 2.).

Nesta seção, veremos como podemos organizar diferentes serviços para trabalhar em conjunto com uma ferramenta conhecida como Docker Compose.

Este processo é conhecido como "orquestração".

Minicurso Docker
UFSJ | Secomp 2025

4.1. Caso de uso: execução de um servidor web com banco de dados

Considere o seguinte serviço escrito em Python com FastAPI e SQLModel:

class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    age: int

engine = db_connect()
def get_db():
    with Session(engine) as session:
        yield session

Db = Annotated[Session, Depends(get_db)]
api = FastAPI()

@api.get("/user")
def list_users(db: Db):
    return db.exec(select(User)).all()
Apenas trecho do código. Todos os códigos apresentados nestes slides estão disponíveis no repositório
UFSJ | Secomp 2025

compose.yml

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: app
    ports:
      - 5432:5432

  app:
    build: .
    environment:
      DATABASE_URL: |
        postgresql+psycopg2://user:password@db:5432/app
    ports:
      - 8000:8000

Dockerfile

FROM python:3.13-alpine

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY server.py .
ENTRYPOINT [
  "fastapi",
  "dev",
  "--host",
  "0.0.0.0",
  "server.py"
]
Apenas trecho do código. Todos os códigos apresentados nestes slides estão disponíveis no repositório

5. Usando Docker para automações

Alguns hosts de repositórios Git como o GitHub e o Forgejo suportam a execução de scripts diretamente em seus servidores dado algum evento, como o push de um novo commit ou a criação de uma nova release.

Estes scripts podem servir múltiplos propósitos e são escritos em shell script.

Podemos nos aproveitar destas pipelines para criar um fluxo de lançamento contínuo (CD; em inglês Continuous Deployment). Ao invés de escrever scripts complexos de bash que comumente não são trivialmente testáveis localmente, podemos usar o Docker, que, novamente, nos provê ambientes reproduzíveis.

Minicurso Docker
UFSJ | Secomp 2025

5.1. Escrevendo workflows para o GitHub

No GitHub, arquivos em .github/workflows/ definem fluxos que podem ser executados em diferentes circunstâncias, a depender da tag on do arquivo.

No exemplo ao lado, vemos um fluxo que emitirá nos logs de execução uma mensagem "Hello, World!"

.github/workflows/hello_world.yml

name: Hello World
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Say hello world
        shell: bash
        run: echo Hello, World!
Minicurso Docker
UFSJ | Secomp 2025
5.2. Workflows para a compilação de código
name: Compile and Run
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # roda uma ação pré-definida encontrada em
      # https://github.com/actions/checkout
      - name: Checkout Repository
        uses: actions/checkout@v4
      - name: Compile code
        shell: bash
        run: gcc main.c -o main
      - name: Run it
        shell: bash
        run: ./main
UFSJ | Secomp 2025

5.3. Recursão? Publicando slides com um workflow

- name: Markdown to HTML
  uses: docker://ghcr.io/marp-team/marp-cli:v4.2.3
  env:
    MARP_USER: root:root
  with:
    args: slides/main.md-o build/index.html --html

- name: Deploy production
  uses: JamesIves/github-pages-deploy-action@v4
  with:
    branch: gh-pages
    folder: ./build/

Este trecho faz parte da definição do workflow neste repositório que realiza a publicação dos slides em https://secomp2025.brenno.codes/

O fluxo utiliza a mesma imagem de Docker que foi usada localmente durante a escrita dos slides, garantindo que a mesma ferramenta, na mesma versão e com as mesmas dependências, está sendo usada. Assim, os resultados são idênticos.

Minicurso Docker
UFSJ | Secomp 2025

5.4. Extra: publicando suas próprias imagens

Imagens de Docker devem ser publicadas em repositórios de imagens. Exemplos destes repositórios são: Docker Hub, GHCR (GitHub Container Registry), e AWS ECR (Elastic Container Registry).

- name: Log in to the Container registry
  uses: docker/login-action@v2
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- name: Define Image Tag
  id: image-tag
  shell: bash
  env:
    USER_AND_REPO: ${{ github.repository }}
  run: echo "tag=ghcr.io/${USER_AND_REPO,,}:latest" >> "$GITHUB_OUTPUT"

- name: Build and Publish Image
  uses: docker/build-push-action@v5
  with:
    push: true
    tags: ${{ steps.image-tag.outputs.tag }}

Cada um possuirá diferentes métodos para a autenticação, mas a ação de publicar uma imagem em qualquer caso está relacionada ao comando docker push.

O exemplo deste slide contém um trecho que poderia ser utilizado para publicar uma nova imagem no GHCR.

Minicurso Docker

Créditos

Brenno Lemos - Escrita do material

José Luis da Cruz Junior - Validação do material, sugestões e correções técnicas e ortográficas

Desbravando Rust

É um livro em português próximo de ser lançado. O livro tem como o objetivo de ser o material definitivo para quem tem interesse de se profissionalizar na linguagem, cobrindo desde o mais simples Hello World até aplicações complexas no mundo real, como a criação de APIs e Lambdas da AWS.

Siga o autor para atualizações sobre seu lançamento: /in/jose-luis-da-cruz-junior

Perguntar se os alunos estão familiares com o conceito de máquinas virtuais

Senão, fazer um paralelo com emuladores (com a diferença de emular hardware+software vs emular primariamente software)

De forma similar, estes slides são "compilados" em HTML por meio de ferramenta de JavaScript que eu propositalmente não instalei no meu computador. Ao invés, eu usei um contêiner com a ferramenta pré-instalada.

Exercitar a execução de contêiner Python interativo para facilitar a transição para o próximo slide

Explicar que, se não usássemos o '--rm', a dependência estaria pelo menos cacheada da próxima execução, mas ainda seria realizada a tentativa de instalá-la novamente

Mostrar o alpine como alternativa ao debian. Mostrar que não é possível rodar um programa compilado pra uma máquina mais nova em uma mais velha (user trixie-slim vs stretch-slim)

Um livro próximo de ser lançado escrito pelo José Luis da Cruz Junior