💧🐳 Dockerizando Aplicações Phoenix/Elixir
Tags: elixir, docker, phoenix, braziliandevs
Published: 2022-04-24 17:33:14

Neste post vamos aprender a colocar nossas aplicações `Phoenix` em contêineres e a gerenciar vários deles utilizando `Docker Compose`.

Para começar, vamos criar uma aplicação sem banco de dados para entender bem como funciona, e depois sim, vamos criar uma aplicação com banco de dados e usando `docker-compose`.

* [⚙️ Criando o Projeto sem Ecto](#criando-o-projeto-sem-ecto)
 * [🐳 Criando a Imagem Docker sem Ecto](#criando-a-imagem-docker-sem-ecto)
* [⚙️ Criando o Projeto com Ecto](#criando-o-projeto-com-ecto)
 * [🐳 Criando a Imagem Docker com Ecto](#criando-a-imagem-docker-com-ecto)
 * [🐳 Criando o arquivo docker-compose](#criando-o-arquivo-docker-compose)
 * [📜 Criando o arquivo SH](#criando-o-arquivo-sh)
 * [🗃 Configurando o banco de dados no docker-compose](#configurando-o-banco-de-dados-no-docker-compose)

[![A Jornada do Autodidata em Inglês](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkoqkmh9hic17tnddjf6.png)](https://go.hotmart.com/R93865620U)


# ⚙️ Criando o Projeto sem Ecto (sem Banco de Dados) <a name="criando-o-projeto-sem-ecto"></a>


* Versões usadas neste projeto:
 - elixir 1.13.3-otp-24
 - erlang 24.1.5


Crie uma aplicação Phoenix com o comando:

```bash
$ mix phx.new dockerizando_sem_ecto --app blog --no-ecto
```

link do projeto no github: https://github.com/maiquitome/dockerizando_sem_ecto


# 🐳 Criando a Imagem Docker sem Ecto  <a name="criando-a-imagem-docker-sem-ecto"></a>


Vamos configurar o contêiner da nossa aplicação __Phoenix__, criando um arquivo com o nome __Dockerfile__ na raiz do projeto com o seguinte conteúdo:

```Dockerfile
# imagem base
# Alpine é uma imagem mínima do Docker baseada no Alpine Linux com um índice de pacotes completo e apenas 5 MB de tamanho!
FROM elixir:1.13.3-alpine

# instalando o gerenciar de pacotes do elixir
RUN mix local.hex --force && \
    mix local.rebar --force
    
# também funciona essa sintaxe:
# RUN mix do local.hex --force, local.rebar --force
 
# copiar tudo da raiz do projeto para o contêiner docker
COPY . .

# instalar as dependencias
RUN mix do deps.get, deps.compile

# executar o servidor
CMD ["mix", "phx.server"]
```

### .dockerignore

Agora podemos criar um arquivo também na raiz do projeto chamado `.dockerignore` para colocarmos os arquivos que devem ser ignorados pelo Docker, ou seja, não vão ir para dentro do contêiner.

Vamos colocar alguns arquivos que podemos querer ignorar, mesmo que alguns deles não estejam no nosso projeto, vamos colocar para exemplo, pois algum dia você pode querer colocar um arquivo semelhante:
```.dockerignore
_build/
deps/
.elixir_ls/
.git
.github
.gitignore
.editorconfig
.circleci
helm/*
terragrunt.hcl
README.md
docker-compose*
```

### Construindo a Imagem
```bash
$ docker build -t app/blog .
```

Executando o comando `docker images` no terminal, podemos constatar que a nossa imagem foi criada:
```bash
$ docker images         
REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app/blog     latest   eae5a6582cdd   3 minutes ago   105MB
```
ou no Docker Desktop:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmv27xi34qbusbf34d7j.png)
 

### Criando um Contêiner
```bash
$ docker run app/blog
```
Executando o comando `docker container ls` ou o comando antigo `docker ps` no terminal, podemos constatar que o nosso contêiner foi criado:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oyqbrbykpll7xxs1l04.png) 

No Docker Desktop:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pk5dty28lfwgwgvujudh.png)

Após, podemos constatar que a aplicação está rodando na porta 4000:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xr2r9k9dgrbqzk98abtw.png)
 
Então, vamos tentar acessar a aplicação:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75jphriyzyqcvj23ufzu.png)

E recebemos a mensagem que este site não pode ser acessado. Isso acontece, pois, a nossa aplicação está na porta 4000 dentro do contêiner. Para acessar precisamos executar o comando abaixo passando uma porta, neste caso a porta 8000:

```bash
$ docker run -p 8000:4000 app/blog 
```
E agora conseguimos acessar a nossa aplicação dentro do contêiner:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56cra6aj7whs8snl5ddp.png)

Caso você receba a mensagem `This page ins't working`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxww0g0jlqufnohndsae.png)

Altere o arquivo `config/dev.exs` trocando `{127, 0, 0, 1}` para `{0, 0, 0, 0}`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmsbm7k8eqo98w2z1zxe.png)
 
### Adicionando um diretório de trabalho (WORKDIR)

Se executarmos o comando `docker run -it app/blog sh` e depois um `ls` podemos notar que os arquivos do contêiner estão misturados com os arquivos da aplicação:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axfru0e0c10o71hmfbmq.png)

Vamos adicionar no aquivo `Dockerfile` o comando `WORKDIR`:
```Dockerfile
FROM elixir:1.13.3-alpine

WORKDIR /app

...
```
Vamos verificar que agora ficou mais organizado:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ou33wbg1a1vgv9lhvsg1.png)
 


# ⚙️ Criando o Projeto com Ecto (com Banco de Dados) <a name="criando-o-projeto-com-ecto"></a>

Agora vamos criar uma aplicação Phoenix com banco de dados:

```bash
$ mix phx.new dockerizando_com_ecto --app blog
```

Link do projeto no github: https://github.com/maiquitome/dockerizando_com_ecto

# 🐳 Criando a Imagem Docker com Ecto  <a name="criando-a-imagem-docker-com-ecto"></a>

Vamos usar o mesmo `Dockerfile` do projeto anterior, criar a nossa imagem e subir um contêiner da nossa aplicação:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8r41psk3tz40cm11uqt.png)

Após isso, recebemos um erro, informando que a conexão com o banco de dados falhou. Para resolvermos isso vamos aprender a usar o `docker-compose`.



# 🐳 Criando o Arquivo docker-compose  <a name="criando-o-arquivo-docker-compose"></a>



O __Docker Compose__ serve para iniciar vários conteiners.
 
Crie um arquivo na raiz do projeto chamado `docker-compose.yml`.

```yml
version: "3" # versão do docker-compose
services: # serviços que precisamos colocar em conteiner
  # e um desses serviços é a nossa aplicação.
  app: # nome da nossa aplicação, pode ser qualquer nome, nesse caso vamos dar o nome de app
    build: 
      context: . # context é o diretório onde a gente está
    ports: # pra executar o docker run precisamos passar a porta
      - "8080:4000" # o `-` no yml significa que é um array
```

Este código do arquivo `docker-compose.yml` irá executar o __Dockerfile__ e, para isso, vamos executar no terminal o comando `docker-compose up`:

```bash
$ docker-compose up
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvflz9gljhrkw5ied49q.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xyxt0t9fkw6ez5jmjg1.png) 
 
Mesmo com o erro do banco de dados, acesse a porta 8080:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duskcapol010t5cap6up.png)

No momento, o `docker-compose` está servindo apenas para fazer o build da imagem do nosso projeto.



# 📜 Criando o arquivo SH <a name="criando-o-arquivo-sh"></a>
 
Remova o comando `CMD ["mix", "phx.server"]` do arquivo `Dockerfile`.

Crie um arquivo na raiz do projeto chamado `docker_dev_start.sh`.

```sh
mix ecto.drop # destrói o banco de dados
mix ecto.setup # cria o banco de dados e executa o arquivo seeds
exec mix phx.server # sobe o servidor
```

Agora vamos fazer executar este script dentro do `docker-compose.yml`. Como estamos em uma máquina Alpine, adicione o seguinte comando `command: /bin/sh docker_dev_start.sh`. Se a máquina fosse um Windows, por exemplo, o comando seria diferente.

```yml
version: "3" # versão do docker-compose
services: # serviços que precisamos colocar em conteiner
  # e um desses serviços é a nossa aplicação.
  app: # nome da nossa aplicação, pode ser qualquer nome, nesse caso vamos dar o nome de app
    build: 
      context: . # context é o diretório onde a gente está
    command: /bin/sh docker_dev_start.sh # executa o script do arquivo docker_dev_start.sh
    ports: # pra executar o docker run precisamos passar a porta
      - "8080:4000" # o `-` no yml significa que é um array
```

Ao executarmos `docker-compose up` recebemos um erro:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghrm391zb7t9bhjzhmmz.png)

Para funcionar, precisamos construir novamente a imagem e, para isso, pode usar o comando:

```bash
$ docker-compose up --build
```



# 🗃 Configurando o banco de dados no docker-compose <a name="configurando-o-banco-de-dados-no-docker-compose"></a>




Mas ainda temos o erro de conexão com o banco de dados, por isso, precisamos incluir o contêiner do postgres e configurar ele:


```yml
version: "3"
services:
  app:
    build: 
      context: .
    command: /bin/sh docker_dev_start.sh
    ports:
      - "8080:4000"
  # adicione a configuração abaixo
  db: 
    image: postgres
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
    ports:
      - "5432:5432"
```
Altere de `localhost` para `db` no arquivo `config/dev.exs`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm60zbisd9nk2gx5291t.png)

E, como alteramos os arquivos, precisamos fazer o build novamente:

```bash
$ docker-compose up --build
```

Agora funciona, mas ainda temos um problema:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tixintwh7ty2qqsy6st.png)

Precisamos fazer com o __db__ execute totalmente primeiro e, pra isso, vamos colocar uns comandos:

```yml
version: "3"
services:
  app:
    build: 
      context: . 
    command: /bin/sh docker_dev_start.sh
    ports:
      - "8080:4000"
    # adicione o depends_on e o links
    depends_on:
      - db # nosso app depende do db para funcionar
    links:
      - db
  db: 
    image: postgres
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
    ports:
      - "5432:5432"
``` 
 
E, como alteramos os arquivos, precisamos fazer o build novamente:

```bash
$ docker-compose up --build
```

E, agora sim, o __db__ executou primeiro:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrnfd8rybucwit3k7ff2.png)

__DICA:__ Podemos executar também em background usando o comando:
```bash
$ docker-compose up -d 
```