Como rodar Jupyter Notebooks dentro do Docker

Um passo-a-passo de como montar sua imagem preparada para rodar Jupyter Notebooks

Erica Bertan
Computando Arte
6 min readMay 25, 2022

--

Para fazer provas de conceito, scripts curtos e descartáveis, até mesmo análises de dados complexas, muita gente já precisou usar um notebook. A popularidade da ferramenta é compreensível, visto que ela é bem prática e visual. Inclusive, usando ferramentas como o Colab, o esforço de preparação do ambiente é nulo!

Mas você não estaria nesse artigo se não tivesse passado da parte de preparação. Seu projeto avançou bastante e você começou a ter que compartilhar o arquivo do notebook com terceiros para que eles pudessem testar e experimentar novas coisas. Repassá-lo começou a ficar inconveniente, mesmo com mecanismos de versionamento:

  • Para rodar seu notebook, é necessário ter um algumas libs instaladas na sua máquina. A versão que você utiliza é diferente da máquina dos colegas e os primeiros bugs por esse motivo começam a aparecer;
  • Ou seu notebook começa a exigir muitos recursos da sua máquina e fica à beira do impraticável continuar usando seu laptop enquanto ele roda;
  • Ou o projeto avança bastante a ponto de você precisar migrar esse notebook para um ambiente remoto, e continuar fazendo modificações no código com pouco esforço de replicação se torna uma tarefa onerosa.

Ou todos esses problemas ao mesmo tempo e muito mais. É nesse momento que talvez você possa considerar usar notebooks dentro de containers, e isso traz muitas vantagens:

  • Facilidade de replicação do ambiente quando mais de uma pessoa precisa usá-lo;
  • Garantia de isolamento do ambiente, o que também torna o primeiro ponto possível. Uma vez que você prepara seu container, os processos do dentro dele rodam sem interferência das configurações da máquina host, ou seja, resolve o famoso “na minha máquina funciona” 😄;
  • Facilitar gerenciamento de recursos, uma vez que você pode alocar e desalocar facilmente os recursos do seu container a medida que for necessário.

Então, neste artigo meu objetivo é mostrar um passo a passo de como construir sua imagem Docker para rodar notebooks para facilitar seu dia a dia.

* A leitura do artigo vai ser mais fácil e fluida se você acompanhar o que for dito com o projeto completo no repositório jupyter-boilerplate no meu Github.

** Aqui vou assumir que conhecimentos básicos como Docker e Notebooks já são conhecidos pelo leitor.

📌 Docker entra no chat: montando uma imagem

Fonte: Jérôme Petazzoni

1. Preparando o requirements.txt

Para preparar uma imagem, possivelmente vamos precisar instalar algumas bibliotecas que não existem previamente na imagem base que estamos usando. Por isso, crie um arquivo requirements.txt com todas as libs que você precisa.

2. Configurando o Dockerfile

O Docker consegue construir imagens a partir das instruções que ele lê em um arquivo chamado Dockerfile. O nosso vai ser curto e fácil de entender. Veja no arquivo abaixo que explicarei linha por linha.

  • Linha 1: o comando FROM indica que estamos começando a construir uma imagem, e com ele setamos a imagem base usada para construir a nossa. Como boa prática, sempre use imagens oficiais. (Guia para boas práticas de escrita do Dockerfile);
  • Linha 3: com o comando WORKDIR setamos o diretório de trabalho, ou seja, quando você subsequentemente rodar um comando ADD, COPY, RUN, etc, esses comandos serão executados nesse diretório. Um meio de ver isso na prática é entrar no shell do container que está de pé, pois é nesse diretório que seremos jogados;
  • Linha 4: O comando ADD copia arquivos de algum lugar de origem para o sistema de arquivos da imagem. No caso, o requirements.txt é que está sendo copiado para dentro da nossa imagem;
  • Linha 5: O RUN executa comandos, e no nosso caso, ele roda o pip para instalar todas as libs que adicionamos anteriormente no arquivo requirements.txt.

Pronto, temos a definição da nossa imagem. Agora, vamos preparar as configurações para lidar com esse container em produção através de um arquivo de especificação do docker-compose.

3. Criando o docker-compose.yml

Você deve estar se perguntando (e com razão) por que precisamos dessa etapa. Para isso, vale explicar a diferença entre usar o docker e o docker-compose: enquanto o primeiro está preparado para lidar com um container, o segundo consegue lidar com vários — o que é bastante útil em produção.

  • Linha 4: aqui precisamos definir o nome do serviço, que no caso é o jupyter_service. Este arquivo pode ter vários outros serviços, a depender da necessidade;
  • Linha 5: o . indica que o Dockerfile vai ser procurado na raiz do projeto para que a imagem possa ser construída. A documentação do Compose dá uma boa ideia sobre o funcionamento desse comando;
  • Linha 6: o entrypoint sobrescreve o entrypoint default definido na imagem. Mais na frente, veremos que a definição desse arquivo scripts/docker-entrypoint.sh é apenas um comando que executa o Jupyter Notebooks, ou seja, ao executar o container poderemos acessar a instância do Jupyter dentro dele;
  • Linha 7: aqui definimos o usuário que iremos acessar os notebooks de dentro do container, e para fins didáticos, setei o root e para que eu tivesse todas as permissões. Com a ausência dele, só acessaríamos o notebook em modo de leitura;
  • Linha 8–9: com o ports eu defino a porta por onde conseguimos acessar o ambiente do Jupyter Notebook dentro do container;
  • Linha 10–11: com o comando env_file definimos as nossas variáveis de ambientes que poderão ser acessadas e usadas dentro do container;
  • Linha 12–13: volumes valem um artigo separado aqui no Computando Arte, mas por hora, só se precisa entender que uma vez que provisionamos um container, e logo depois desprovisionamos esses recursos, os dados não são persistidos. Uma maneira de persisti-los é justamente fazendo o uso de volumes.

4. Exclua arquivos não necessários com o .dockerignore

Para quem lida com o git no dia-a-dia, fica trivial entender o propósito do arquivo .dockerignore. De maneira análoga, à medida que for desenvolvendo seu projeto e não quiser que certos arquivos sejam incorporados no build da sua imagem, basta adicionar as extensões .dockerignore e o Docker o ignorará.

📌 Rodando um notebook

Temos nossa imagem pronta, mas ainda falta passarmos por um ponto crucial: como rodar o notebook dentro do container? É, na verdade, muito simples. Anteriormente, definimos um entrypoint dentro do nosso arquivo docker-compose.yml e ele tinha o valor scripts/docker-entrypoint.sh. Vamos defini-lo agora:

1. Criando o arquivo scripts/docker-entrypoint.sh

Vamos começar pelo mais simples, se você tivesse rodando um notebook na sua máquina, você apenas digitaria jupyter notebook e a mágica aconteceria. Aqui, só temos algumas sofisticações a mais, mas é puramente a mesma ideia.

  • --ip: com este parâmetro estamos definindo o ip por onde conseguiremos acessar o ambiente do Jupyter pelo browser
  • --port: com este parâmetro definimos a porta por onde acessaremos o ambiente do Jupyter pelo browser
  • --allow-root: como estou rodando o notebook no modo root, na ausência dele o seguinte erro é disparado: “Running as root is not recommended. Use — allow-root to bypass.”. Aqui, o allow-root é apenas uma forma de contornar esse problema.

E o que significa o PYTHONPATH=$PYTHONPATH:/home/src neste início? Apenas que estamos setando o PYTHONPATH dentro do container como variável de ambiente com os valores já existentes somado ao workdir do container (/home/src), assim o Python consegue enxergar os módulos e pacotes no diretório do container também.

2. Usando o docker-compose para executar nosso notebook

Agora podemos testar nosso notebook dentro do container. Para isso, basta executar os comandos

  • docker-compose build: para construir a imagem e prepará-la para execução no container
  • docker-compose up: para subir o container. No terminal, deve aparecer um link para clicar e o Jupyter abrir em seu terminal
Links para acessar o Jupyter
  • docker-compose down: para matar seu container e desalocar os recursos.

Conclusão

Pronto, agora você pode rodar notebooks dentro de um Docker container. Para construir esse boilerplate e também para o meu dia-a-dia no uso e entendimento de Docker, boa parte do que aprendi foi lendo a documentação do Compose, pois ela é bem completa. Então, fica aí a recomendação de leitura!

E, claro, caso tenha qualquer dúvida, ou mesmo sugestão de melhoria em algum ponto do projeto e do post, fique à vontade para comentar aqui que responderei assim que possível 😃

--

--

Erica Bertan
Computando Arte

Love to learn and sometimes I write when I’m inspired. Data Engineer @ Loggi