├── README.md ├── assets ├── docker-banner-0.jpg ├── docker-banner-1.jpg ├── docker-banner-2.jpg ├── docker-banner-3.jpg ├── docker-banner-4.jpg ├── docker-banner-5.jpg ├── docker-banner.jpg ├── docker-hub.png ├── docker-run-node.gif └── git-hello.gif ├── lvl.0.configure └── README.md ├── lvl.1.containers ├── README.md └── app │ ├── index.html │ ├── package.json │ └── server.js ├── lvl.2.images ├── README.md └── app │ ├── Dockerfile │ ├── index.html │ ├── package.json │ ├── server.js │ └── start.sh ├── lvl.3.network ├── README.md └── app │ ├── Dockerfile │ ├── index.html │ ├── package.json │ ├── server.js │ └── start.sh ├── lvl.4.volumes ├── README.md └── app │ ├── Dockerfile │ ├── index.html │ ├── package.json │ ├── server.js │ └── start.sh └── lvl.5.compose ├── README.md └── app ├── Dockerfile ├── docker-compose.yaml ├── index.html ├── package.json ├── server.js └── start.sh /README.md: -------------------------------------------------------------------------------- 1 | # Oficina de Docker 2 | 3 | ![Banner Docker](./assets/docker-banner.jpg) 4 | 5 | Repositório da oficina de docker para a IV semana de informática do Centro de Ensino Superior do Seridó - CERES/UFRN. 6 | 7 | Utilizaremos um aplicativo web escrito em Node com o framework Express como base para fazer a conteinerização e mostrar as vatanges da estratégia de utilizar Docker no desenvolvimento, porém, **não será necessário conhecimentos em Node para fazer essa oficina** 8 | 9 | ## Recomendações 10 | 11 | 1. Faça o clone desse repositório com o comando ``git clone https://github.com/victorhundo/docker-guide.git`` e verifique os arquivos de configuração paralelamente com as instruções do material da oficina. 12 | 13 | 2. Para fazer essa oficina recomendamos que todos os comandos sejam **executados manualmente** no terminal do seu computador, é importante que no fim dessa oficina você saiba o que cada paramêtro faz. 14 | 15 | ## Levels 16 | Esse repositório é dividido em pequenos módulos que abordam funcionalidades diferentes do Docker. São eles: 17 | 18 | 0. [Lvl 0 - Configure](http://github.com/victorhundo/docker-ceres/tree/master/lvl.0.configure) 19 | 1. [Lvl 1 - Containers](http://github.com/victorhundo/docker-ceres/tree/master/lvl.1.containers) 20 | 2. [Lvl 2 - Images](http://github.com/victorhundo/docker-ceres/tree/master/lvl.2.images) 21 | 3. [Lvl 3 - Network](http://github.com/victorhundo/docker-ceres/tree/master/lvl.3.network) 22 | 4. [Lvl 4 - Volumes](http://github.com/victorhundo/docker-ceres/tree/master/lvl.4.volumes) 23 | 5. [Lvl 5 - Compose](http://github.com/victorhundo/docker-ceres/tree/master/lvl.5.compose) 24 | -------------------------------------------------------------------------------- /assets/docker-banner-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-0.jpg -------------------------------------------------------------------------------- /assets/docker-banner-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-1.jpg -------------------------------------------------------------------------------- /assets/docker-banner-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-2.jpg -------------------------------------------------------------------------------- /assets/docker-banner-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-3.jpg -------------------------------------------------------------------------------- /assets/docker-banner-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-4.jpg -------------------------------------------------------------------------------- /assets/docker-banner-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner-5.jpg -------------------------------------------------------------------------------- /assets/docker-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-banner.jpg -------------------------------------------------------------------------------- /assets/docker-hub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-hub.png -------------------------------------------------------------------------------- /assets/docker-run-node.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/docker-run-node.gif -------------------------------------------------------------------------------- /assets/git-hello.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorhundo/docker-guide/98c22d8f2261089b1422d851b954e195e3cd7158/assets/git-hello.gif -------------------------------------------------------------------------------- /lvl.0.configure/README.md: -------------------------------------------------------------------------------- 1 | # Lvl. 0 - Configure 2 | 3 | ![Banner Docker](../assets/docker-banner-0.jpg) 4 | 5 | Antes de começar nossos estudos práticos sobre Docker é necessário configurar o ambiente para que possamos instalar-lo, além do docker, também vamos instalar o ``docker-compose`` que veremos no Lvl.5. 6 | 7 | Para essa oficina vamos utilizar uma máquina com Debian. Caso seu computador não seja esse sistema, acessem a [documentação oficial do docker](https://docs.docker.com/install/overview/) para saber os procedimentos de instalação em outras plataformas. 8 | 9 | ## Instalação 10 | 11 | ### Docker 12 | 13 | **1.** Instae os pacotes que permitem o apt usar repositórios com `https`. 14 | 15 | ``` 16 | $ sudo apt-get install \ 17 | apt-transport-https \ 18 | ca-certificates \ 19 | curl \ 20 | gnupg2 \ 21 | software-properties-common 22 | ``` 23 | 24 | **2.** Adicione a chave GPG oficial do Docker 25 | 26 | ``` 27 | $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - 28 | ``` 29 | 30 | **3.** Adicione o repositório Docker no apt 31 | 32 | ``` 33 | $ sudo add-apt-repository \ 34 | "deb [arch=amd64] https://download.docker.com/linux/debian \ 35 | $(lsb_release -cs) \ 36 | stable" 37 | ``` 38 | 39 | **4.** Instale o Docker! 40 | 41 | ``` 42 | $ sudo apt-get update && sudo apt-get install docker-ce 43 | ``` 44 | 45 | **5.** [opcional] Adicione um usuário que não seja o root a executar comandos docker. Será necessário fazer logout/login para que as mudanças sejam efetivadas. 46 | 47 | ``` 48 | $ sudo usermod -aG docker $USER 49 | ``` 50 | 51 | ### Docker compose 52 | 53 | **1.** Instale o ``pip``. 54 | 55 | ``` 56 | $ sudo apt-get install python-pip 57 | ``` 58 | 59 | **2.** Insale o Docker-compose 60 | 61 | ``` 62 | $ sudo pip install docker-compose 63 | ``` 64 | 65 | #### [Próximo -> Lvl 1 - Containers](http://github.com/victorhundo/docker-ceres/tree/master/lvl.1.containers) 66 | -------------------------------------------------------------------------------- /lvl.1.containers/README.md: -------------------------------------------------------------------------------- 1 | # Containers 2 | 3 | ![Banner Docker](../assets/docker-banner-1.jpg) 4 | 5 | Agora que estamos com ambiente configurado para executar Docker e o docker-compose, está na hora de colocar a mão na massa! 6 | 7 | 8 | **Esse módulo é divido em:** 9 | 10 | 1. [Intrdução](#introdução) 11 | 2. [Hello World](#hello-world) 12 | 3. [Criando Containers](#criando-containers) 13 | 4. [Alguns Comandos Úteis](#comandos-Úteis) 14 | 5. [Primeiro Build com Containers](#Primeiro-Build) 15 | 16 | ## Introdução 17 | 18 | O docker é uma ferramenta para criar e gerenciar containers, que assim como o nome sugere, é um compartimento para armazenar e/ou transportar objetos, no nosso caso o compartimento é criado através de **virtualização** em que memoria e o processamento são separados para o uso do exclusivo do container. 19 | 20 | É importante frisar que container **não é uma máquina virtual**, os containers utiliza-se do próprio kernel da máquina hospedeira, enquanto as máquinas virtuais também virtualizam o kernel. Com containers é possivel **isolar bibliotecas e binários** dentro do mesmo SO sem a necessidade de virtualizar um novo sistema. 21 | 22 | ![Diferença container/vm](https://cdn-images-1.medium.com/max/1200/1*9WTam0ymAdSTrfbr7aV6oQ.png) 23 | 24 | ## Hello World 25 | 26 | Para verificar que seu ambiente está configurado de forma correta, basta executar o seguinte comando abaixo, se aparecer a mensagem `Hello from Docker!` tudo ocorreu bem: 27 | 28 | ``` 29 | docker run hello-world 30 | ``` 31 | ![Diferença container/vm](../assets/git-hello.gif) 32 | 33 | Ao executar esse comando, nós chamamos o binário do docker, criamos um container a partir da opção ``run`` e dizemos que o container que será criado é da `imagem` ``hello-world:latest``. (vamos falar de imagens mais a frente). 34 | 35 | ## Criando Containers 36 | 37 | Vamos agora criar um container que seja mais útil e a partir dele analisar os principais comandos de criação de um container. Vamos criar um container do **Node**. Para isso executem: 38 | 39 | ``` 40 | docker run -it node bash 41 | ``` 42 | 43 | ![Diferença container/vm](../assets/docker-run-node.gif) 44 | 45 | Dessa vez criamos um cointaner passando o parametro ``-it`` além de adicionar um ``bash`` no final, o parametro it signifca que você ganhará um **terminal interativo** ao exectuar o container, caso você não coloque essa opção o container executará suas instruções e quando o **seu comando principal** terminar de executar ele irá morrer. O comando ``bash`` irá executar o comando do shell no container, então dessa forma conseguimos ter um **shell interativo dentro do container**. 46 | 47 | Reparem que o hostname foi alterado, nesse momento estou dentro do container ``node:latest``, para verificar isso eu executei o comando ``node -v`` que me retornou a versão 10.11, enquanto que na minha máquina, como também pode ser notado, utilizo a versão 8.9 do node. 48 | 49 | **O node que roda na minha máquina não tem relação nenhuma com o node do container**, com esse isolamento podemos trabalhar com diversos serviços que utilizam versões diferentes da mesma tecnologia sem que entrem em conflitos. 50 | 51 | Dentro do container, ao listar os processos que estão rodando com o comando ``ps`` vamos observar a seguinte saida: 52 | 53 | ``` 54 | PID TTY TIME CMD 55 | 1 pts/0 00:00:00 bash 56 | 8 pts/0 00:00:00 ps 57 | 58 | ``` 59 | 60 | Só existe 2 processos rodando: o ps que acabamos de executar e o bash que executamos na criação do container, nesse caso, o processo principal é o ``bash`` (podemos observar pelo pid), caso esse processo morra o container também irá morrer. 61 | 62 | Para sair do container **sem matar o processo principal** execute ``Ctrl-p + q``. 63 | 64 | ## Comandos Úteis 65 | 66 | Ao executar o útlimo comando que citamos acima, você terá o shell do seu computador de volta. Como você manteve o processo principal do container ainda rodando, significa que o container também está rodando! 67 | 68 | ### Listando Containers 69 | para verificar os containers que estão rodando na sua máquina execute o comando: 70 | 71 | ``` 72 | docker ps 73 | ``` 74 | 75 | A saida será algo como: 76 | 77 | ``` 78 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 79 | 668698b20989 node "bash" 7 minutes ago Up 7 minutes kind_northcutt 80 | ``` 81 | 82 | 1. **CONTAINER ID** - Identificador único do container. 83 | 2. **IMAGE** - Imagem que deu origem ao container. 84 | 3. **COMMAND** - Comando principal do container. 85 | 4. **CREATED** - Data na qual o container foi criado; 86 | 5. **STATUS** - Estado do container, pode ser: Up, ou Exited 87 | 6. **PORTS** - Portas de rede que o container está utilizando/expondo. 88 | 7. **NAMES** - O nome do container, para ser mais amigável de identificar no lugar do ID, na criação do container é possivel personalizar o nome com o para metro --name, por exemplo ``docker run --name My-Node -it node bash`` 89 | 90 | ### Adicionando ou retirando arquivos do container 91 | 92 | Ainda utilizando o exemplo do container node que criamos **kind_northcutt** (node gerado automaticamente, provavelmente será outro nome no seu computador), vamos adicionar os arquivos que esta na pasta ``/lvl1.containers/app`` nesse repositório. 93 | 94 | A pasta ``app`` tem uma aplicação hello world em node, para tranferir os arquivos para dentro do container faça: 95 | 96 | ``` 97 | docker cp app/ NOME_DO_CONTAINER:/opt/ 98 | ``` 99 | 100 | Como já dito, o `NOME_DO_CONTAINER` mudará de máquina pra máquina, verifique executando o comando ``docker ps`` já visto. 101 | 102 | ### Entrando no container 103 | 104 | Até agora apenas entramos no container na hora de sua criação, para que possamos voltar ao ambiente do container devemos executar o comando: 105 | 106 | ``` 107 | docker exec -it NOME_DO_CONTAINER bash 108 | ``` 109 | 110 | A opção **exec** é voltado pra executar comandos dentro do container, porém, utilizamos da mesma estratégia quando criamos o container: passamos o parametro de terminal interativo e chamamos o shell. 111 | 112 | Uma vez de volta ao container, acesse a pasta ``/opt/app`` em que copiamos os arquivos de `app`, os arquivos devem ser listados de forma parecida como abaixo: 113 | 114 | ``` 115 | root@668698b20989:~# cd /opt/app 116 | root@668698b20989:/opt/app# ls 117 | index.html index.js style.css 118 | root@668698b20989:/opt/app# 119 | ``` 120 | 121 | ## Primeiro Build 122 | 123 | Agora que estamos com os arquivos node dentro do nosso container, podemos executar o primeiro build dentro do container, para isso execute: 124 | 125 | ``` 126 | npm install 127 | ``` 128 | 129 | e para subir nosso servidor web: 130 | 131 | ``` 132 | npm start 133 | ``` 134 | 135 | Se tudo correu bem você terá a seguinte saida: 136 | 137 | ``` 138 | root@668698b20989:/opt/app# npm start 139 | 140 | > node-hello-world@0.0.1 start /opt/app 141 | > node server.js 142 | 143 | App listening on port 3000 144 | Press Ctrl+C to quit. 145 | ``` 146 | 147 | Agora pressione novamente ``Ctrl-p + q`` para sair do container **salvando o estado atual** e em seguida pare o container que criamos com o comando: 148 | 149 | ``` 150 | docker stop NOME_DO_CONTAINER 151 | ``` 152 | 153 | e em seguida o remova: 154 | 155 | ``` 156 | docker rm NOME_DO_CONTAINER 157 | ``` 158 | 159 | Nesse módulo aprendemos o que é um container, executamos comandos básicos do docker e fizemos o build e deploy de uma aplicação web simples, porém, tudo de forma manual. No próximo módulo vamos aprender como automatizar o processo de build e deploy da aplicação. Criaremos nossa própria imagem! 160 | 161 | #### [Próximo -> Lvl 2 - Images](http://github.com/victorhundo/docker-ceres/tree/master/lvl.2.images) 162 | -------------------------------------------------------------------------------- /lvl.1.containers/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Docker World! 9 | 10 | 11 | 12 | 13 | 14 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |

Hello Docker World

186 | 193 |
194 |
195 |
196 | 198 |

Make Anything with Docker.

199 |
200 |
201 |
202 |

by @victorhundo.

203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lvl.1.containers/app/package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "node-hello-world", 4 | "description": "Servidor web para oficina de Docker", 5 | "version": "0.0.1", 6 | "private": true, 7 | "license": "Apache Version 2.0", 8 | "author": "Victor Hugo", 9 | "scrips":{ 10 | "start": "node index.js" 11 | }, 12 | "engines": { 13 | "node": ">=4.3.2" 14 | }, 15 | "dependencies": { 16 | "express": "^4.14.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lvl.1.containers/app/server.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | var express = require('express'); 3 | var app = express(); 4 | const PORT = process.env.PORT || 3000; 5 | app.get('/', function (req, res) { 6 | res.sendfile('./index.html'); 7 | }); 8 | 9 | // Start the server 10 | app.listen(PORT, () => { 11 | console.log(`App listening on port ${PORT}`); 12 | console.log('Press Ctrl+C to quit.'); 13 | }); 14 | -------------------------------------------------------------------------------- /lvl.2.images/README.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | ![Banner Docker](../assets/docker-banner-2.jpg) 4 | 5 | **Esse módulo é divido em:** 6 | 7 | 1. [Intrdução](#introdução) 8 | 2. [Docker Hub](#docker-hub) 9 | 3. [Dockerfile](#dockerfile) 10 | 4. [Criando a Primeira Imagem](#primeira-imagem) 11 | 12 | 13 | ## Introdução 14 | 15 | No módulo anterior nós copiamos os arquivos que estava na pasta ``/lvl.1.containers/app`` no repositório para que fosse possivel fazer o build e o deploy da nossa aplicação web exemplo, essa prática não é recomendada porque para uma aplicação grande contém vários arquivos além de outros programas/bibliotecas que precisam está instaladas no **ambiente da aplicação.** A melhor forma de customizar o container com as configurações necessárias para que sua aplicação funcione da melhor forma possivel é através de **imagens.** 16 | 17 | ## Docker Hub 18 | Uma imagem docker é um container pré-configurado em que será utilizado como referencia **para criar containers**. No nosso exemplo, usamos a imagem **oficial** do Node, assim como containers, podemos visualizar as imagens que estão instaladas na nossa máquina digitando o comando: 19 | 20 | ``` 21 | docker image ls 22 | ``` 23 | A saida deve ser algo como: 24 | 25 | ``` 26 | REPOSITORY TAG IMAGE ID CREATED SIZE 27 | node latest 8672b25e842c 10 days ago 674MB 28 | hello-world latest 4ab4c602aa5e 3 weeks ago 1.84kB 29 | ``` 30 | 31 | As imagens são baixadas do [Docker Hub](https://hub.docker.com/) que é o "github dos containers", lá você poderá encontrar a imagem base para os seus serviços: 32 | 33 | 34 | ![Docker Hub](../assets/docker-hub.png) 35 | 36 | Na data em que esse documento foi escrito esses foram as principais imagens oficiais que estão no Docker Hub. Em cada repoistório de imagem, existe as instruções de como criar o container. 37 | 38 | ## Dockerfile 39 | 40 | Mesmo que no Docker Hub exista diversas soluções já desenvolvidas, você pode precisar de uma imagem específica da sua aplicação, uma imagem em que você e outras pessoas possam baixar e utilizar sua aplicação. Para isso é necessário criar um **Dockerfile**. 41 | 42 | Dentro da pasta ``/lvl.2.images/app/`` existe um arquivo chamadao ``Dockerfile``, nele terá as instruções para criar a imagem da nossa aplicação web exemplo. 43 | 44 | A primeira linha do Dockerfile diz qual será a imagem usada como base para criar a nossa própria imagem para isso usa-se o ``FROM``, no nosso caso será a imagem do Node na última versão disponível no Docker hub. Na segunda linha o ``LABEL`` adiciona meta dados à imagem, estamos colocando o nome de quem mantém essa imagem. 45 | 46 | Pedimos que **acessem o arquivo ./lvl.2.images/app/Dockerfile** e altere o ``LABEL`` para o seu nome e email. 47 | 48 | ### FROM/LABEL 49 | ``` 50 | FROM node:latest 51 | LABEL maintainer "Victor Hugo " 52 | ``` 53 | ### WORKDIR 54 | Logo em seguinda em ``WORKDIR`` que nos vai dizer em qual pasta estará nosso diretório de trabalho, ao acessar o container essa será a pasta padrão. Utilizar workdir é importante para não se preocupar em caminhos absolutos na hora de rodar scripts. 55 | 56 | ``` 57 | WORKDIR /opt 58 | ``` 59 | ### RUN 60 | Agora executaremos comandos de instalação no container, podemos executar qualquer comando usando ``RUN``. O primeiro executa a instalação do nmap e em seguida remove o cache gerado pelo apt, porque **o container deve ser efêmero e o mais leve possível**, o seguida instala o pacote ``nodemon`` do npm. 61 | 62 | ``` 63 | RUN apt-get update && apt-get install -y \ 64 | nmap \ 65 | && rm -rf /var/lib/apt/lists/* 66 | 67 | RUN npm install -g nodemon 68 | ``` 69 | ### EXPOSE 70 | 71 | ``EXPOSE`` está relacionada a rede do container, ele informará que a **porta 3000 do container estará exposta** , essa informação é importante para a próxima parte dessa oficina quando falaremos da rede no Docker. 72 | 73 | ``` 74 | EXPOSE 3000 75 | ``` 76 | 77 | ### COPY 78 | Em seguida copiamos os arquivos da nossa aplicação web exemplo que estão no mesmo diretório do Dockerfile e colocando na pasta ``/opt`` dentro do container (a pasta que é nosso diretório de trabalho). Para isso usamos ``COPY``. 79 | 80 | ``` 81 | COPY ./* /opt/ 82 | ``` 83 | 84 | ### CMD 85 | Por fim ``CMD`` chamará o comando principal do container, no nosso caso escolhemos que fosse o shell script ``start.sh`` que está em ``/lvl.2.images/app/`` porque nos dá a liberdade de executar várias instruções, dentro de start.sh está os comandos ``npm install`` e ``npm start``. Se start.sh parar de executar o container também irá parar. 86 | 87 | ``` 88 | CMD [ "bash", "/opt/start.sh"] 89 | ``` 90 | ### Aprenda na prática 91 | Para aprender mais sobre Dockerfiles e aprender na prática, recomendamos o [repositório da Jessie Frazelle](https://github.com/jessfraz/dockerfiles), lá você encontrará inúmeros dockerfiles, inclusive que utilize interfaces gráficas ou que explorem redes como o container do Tor que ela fez. 92 | 93 | ## Primeira imagem 94 | 95 | Agora que aprendemos os conceitos e os caminhos para criar um Dockerfile, chegou a hora de montar nossa primeira imagem! Para isso entrem na pasta ``app`` desse módulo e executem: 96 | 97 | ``` 98 | docker build . -t my_node:1 99 | ``` 100 | 101 | Se tudo ocorreu bem, ao executamos ``docker images ls``, veremos que nossa imagem com a tag 1 foi criada. 102 | 103 | ``` 104 | REPOSITORY TAG IMAGE ID CREATED SIZE 105 | my_node 1 828e3f079092 27 seconds ago 723MB 106 | node latest 8672b25e842c 11 days ago 674MB 107 | hello-world latest 4ab4c602aa5e 3 weeks ago 1.84kB 108 | ``` 109 | Agora que temos a imagem da nossa aplicação web podemos criar um container a partir dela, pra isso executamos: 110 | 111 | ``` 112 | docker run -d --name my_app my_node:1 113 | ``` 114 | Dessa vez executamos utilizando o parametro "detached" -d, esse parametro é o oposto quando utilizamos -it, ele criará o container em "background", para visualizar as informações de criação do container execute: 115 | 116 | ``` 117 | docker logs -f my_app 118 | ``` 119 | 120 | Nesse módulo aprendemos os conceitos por trás da imagem do docker e criamos uma para a nossa aplicação web exemplo utilizando **Dockerfile**, porém ainda não acessamos nossa aplicação, no próximo módulo estudaremos a parte de rede dos containers e como fazer para acessar nossa aplicação web! 121 | 122 | #### [Próximo -> Lvl 3 - Network](http://github.com/victorhundo/docker-ceres/tree/master/lvl.3.network) 123 | -------------------------------------------------------------------------------- /lvl.2.images/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | LABEL maintainer "Victor Hugo " 3 | 4 | WORKDIR /opt 5 | 6 | RUN apt-get update && apt-get install -y \ 7 | nmap \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN npm install -g nodemon 11 | 12 | EXPOSE 3000 13 | 14 | COPY ./* /opt/ 15 | 16 | CMD [ "bash", "/opt/start.sh"] 17 | -------------------------------------------------------------------------------- /lvl.2.images/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Docker World! 9 | 10 | 11 | 12 | 13 | 14 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |

Hello Docker World

186 | 193 |
194 |
195 |
196 | 198 |

Make Anything with Docker.

199 |
200 |
201 |
202 |

by @victorhundo.

203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lvl.2.images/app/package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "node-hello-world", 4 | "description": "Servidor web para oficina de Docker", 5 | "version": "0.0.1", 6 | "private": true, 7 | "license": "Apache Version 2.0", 8 | "author": "Victor Hugo", 9 | "scrips":{ 10 | "start": "node index.js" 11 | }, 12 | "engines": { 13 | "node": ">=4.3.2" 14 | }, 15 | "dependencies": { 16 | "express": "^4.14.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lvl.2.images/app/server.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | var express = require('express'); 3 | var app = express(); 4 | const PORT = process.env.PORT || 3000; 5 | app.get('/', function (req, res) { 6 | res.sendfile('./index.html'); 7 | }); 8 | 9 | // Start the server 10 | app.listen(PORT, () => { 11 | console.log(`App listening on port ${PORT}`); 12 | console.log('Press Ctrl+C to quit.'); 13 | }); 14 | -------------------------------------------------------------------------------- /lvl.2.images/app/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | npm start 5 | -------------------------------------------------------------------------------- /lvl.3.network/README.md: -------------------------------------------------------------------------------- 1 | # Network 2 | 3 | ![Banner Docker](../assets/docker-banner-3.jpg) 4 | 5 | 6 | **Esse módulo é divido em:** 7 | 8 | 1. [Drivers](#drivers) 9 | 2. [Portas](#portas) 10 | 3. [Conectando dois containers](#conectando-dois-containers) 11 | 12 | ## Drivers 13 | Uma das razões de que utilizar os serviços em containers ser tão poderoso é porque eles podem ser conectados entre si, os sistemas de rede do Docker são plugáveis usando **Drivers**, por padrão o Docker oferece 5 drivers: 14 | 15 | 1. Bridge - Esse é o driver que será usado caso você não especifique qual quer utilizar, esse driver criará uma rede específica para o container com comunicação com a rede da máquina real. 16 | 2. Host - Esse driver utilizará a mesma interface de rede da máquina real, logo, se o container utilizar a porta 80, por exemplo, será a porta 80 da máquina real. 17 | 3. Overlay - Utilizado para clusterização de containers. 18 | 4. Macvlan - Permite que o container receba um endereço MAC fazendo com que ele seja percebido como um despositivo físico. 19 | 5. none - Desabilita o uso de rede para o container. 20 | 21 | A escolha dos drivers é feito na criação do container usando o parametro ``--net``, por exemplo: 22 | 23 | ``` 24 | docker run --net=host httpd:latest 25 | ``` 26 | 27 | **Nessa oficina iremos utilizar apenas o driver bridge.** 28 | 29 | ## Portas 30 | 31 | Serviços em redes são acessados através de portas, páginas http (80), servidores de email (25), login remote (22), etc, precisam especificar além do enderço IP a porta de comunicação. 32 | 33 | A nossa aplicação web exemplo roda na ``porta 3000``, para que possamos ter acesso na nossa máquina real, devemos criar o container com o seguinte comando: 34 | 35 | ``` 36 | docker run -d --name my_app -p 1915:3000 my_node:1 37 | ``` 38 | 39 | Repare que agora nós adicionamos mais um parametro na criação do container, o parametro ``-p`` significa que ao escutar na máquina real na porta ``1915`` quem responderá é o serviço que está rodando na porta ``3000`` dentro do container. Utilizamos 1915 mas poderiamos utilizar qualquer outra porta. 40 | 41 | 42 | Modifique o comando acima, e no lugar de 1915 adicione a data do seu aniversáro 43 | 44 | E em seguida acesse em um navegador web o endereço http://localhost:DATA_ANIVERSARIO 45 | 46 | Repare que podemos subir inúmeros containers do mesmo serviços em portas diferentes, assim, além de isolar memória, processamento, biblioteca/binários, o Docker também isola a rede para o funcionamento do container. 47 | 48 | ## Conectando dois containers 49 | 50 | Até agora nós trabalhamos apenas com um container de aplicação web, mas geralmente uma apliação web também precisa de um banco de dados para funcionar, nós teríamos duas opções: 51 | 52 | 1. Instalar nosso banco de dados dentro do container da aplicação web 53 | 2. Criar um container apenas para o banco de dados 54 | 55 | A segunda opção é a melhor prática, porque adicionando o container junto com a aplicação web tornará o container muito pesado e irá tirar o foco do container de isolamento do serviço, então a ideia é **1 serviço para 1 container**. 56 | 57 | Utilizaremos a imagem oficial do Mysql para criar nosso container do BD, utilizando as instruções que tem no repositório no Docker Hub, fazemos: 58 | 59 | ``` 60 | docker run -d --name bd -e MYSQL_ROOT_PASSWORD=senha mysql:latest 61 | ``` 62 | 63 | O comando acima apresenta um **novo parametro** o ``-e`` criará uma variavel de ambiente dentro do container, nesse caso ``MYSQL_ROOT_PASSWORD``, que receberá o valor **"senha"**, variaveis de ambientes são utéis para personalizar pequenos ajustes na criação do container. 64 | 65 | Ao executar ``docker ps``, deverá mostrar nossos 2 containers: 66 | 67 | ``` 68 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 69 | 20ce701cb413 mysql:latest "docker-entrypoint..." 5 seconds ago Up 3 seconds 3306/tcp, 33060/tcp bd 70 | 4f263e37bf9e my_node:1 "bash /opt/start.sh" 29 minutes ago Up 29 minutes 0.0.0.0:1915->3000/tcp my_app 71 | ``` 72 | 73 | Para que possamos fazer com que nossa aplicação se conecte com o container do mysql, precisamos remover o container da nossa aplicação e recria-lo: 74 | 75 | Removendo: 76 | ``` 77 | docker stop my_app 78 | docker rm my_app 79 | ``` 80 | 81 | Recriando com conexão com o container do BD: 82 | ``` 83 | docker run -d --name my_app -p 1915:3000 --link bd:my_bd my_node:1 84 | ``` 85 | 86 | O parametro ``--link`` informa ``NOME_DO_CONTAINER:APELIDO``, "nome do container" é o nome do container que vamos fazer a conexão e o apelido é por qual nome dentro do container irá reconhece-lo. Para verificar se a conexão de certo, vamos acessar o container da aplicação: 87 | 88 | ``` 89 | docker exec -it my_app bash 90 | ``` 91 | 92 | E em seguida vamos executar o nmap, esse programa varre as portas diponiveis de um determinado host, no nosso caso será o container do banco de dados, assim: 93 | 94 | ``` 95 | nmap my_bd 96 | ``` 97 | 98 | A saida deve ser algo como: 99 | ``` 100 | Nmap scan report for my_bd (172.17.0.3) 101 | Host is up (0.000027s latency). 102 | Not shown: 999 closed ports 103 | PORT STATE SERVICE 104 | 3306/tcp open mysql 105 | MAC Address: 02:42:AC:11:00:03 (Unknown) 106 | 107 | Nmap done: 1 IP address (1 host up) scanned in 1.63 seconds 108 | ``` 109 | Repare que a porta padrão do mysql ``3306`` está aberta, logo, se nossa aplicação for fazer alguma consulta ao banco de dados poderá ser feito utulizando o "my_bd". 110 | 111 | #### [Próximo -> Lvl 4 - Volumes](http://github.com/victorhundo/docker-ceres/tree/master/lvl.4.volumes) 112 | -------------------------------------------------------------------------------- /lvl.3.network/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | LABEL maintainer "Victor Hugo " 3 | 4 | WORKDIR /opt 5 | 6 | RUN apt-get update && apt-get install -y \ 7 | nmap \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN npm install -g nodemon 11 | 12 | EXPOSE 3000 13 | 14 | COPY ./* /opt/ 15 | 16 | CMD [ "bash", "/opt/start.sh"] 17 | -------------------------------------------------------------------------------- /lvl.3.network/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Docker World! 9 | 10 | 11 | 12 | 13 | 14 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |

Hello Docker World

186 | 193 |
194 |
195 |
196 | 198 |

Make Anything with Docker.

199 |
200 |
201 |
202 |

by @victorhundo.

203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lvl.3.network/app/package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "node-hello-world", 4 | "description": "Servidor web para oficina de Docker", 5 | "version": "0.0.1", 6 | "private": true, 7 | "license": "Apache Version 2.0", 8 | "author": "Victor Hugo", 9 | "scrips":{ 10 | "start": "node index.js" 11 | }, 12 | "engines": { 13 | "node": ">=4.3.2" 14 | }, 15 | "dependencies": { 16 | "express": "^4.14.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lvl.3.network/app/server.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | var express = require('express'); 3 | var app = express(); 4 | const PORT = process.env.PORT || 3000; 5 | app.get('/', function (req, res) { 6 | res.sendfile('./index.html'); 7 | }); 8 | 9 | // Start the server 10 | app.listen(PORT, () => { 11 | console.log(`App listening on port ${PORT}`); 12 | console.log('Press Ctrl+C to quit.'); 13 | }); 14 | -------------------------------------------------------------------------------- /lvl.3.network/app/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | npm start 5 | -------------------------------------------------------------------------------- /lvl.4.volumes/README.md: -------------------------------------------------------------------------------- 1 | # Volumes 2 | 3 | ![Banner Docker](../assets/docker-banner-4.jpg) 4 | 5 | 6 | **Esse módulo é divido em:** 7 | 8 | 1. [Introdução](#intrdução) 9 | 2. [Tipos de volumes](#tipos-de-volumes) 10 | 3. [Deixando BD persistente](#deixando-bd-persistente) 11 | 4. [Criando container para desenvolvimento](#criando-container-para-desenvolvimento) 12 | 13 | 14 | ## Introdução 15 | Até agora nós vimos maneiras de utilizar containers para pegar sistemas já desenvolvido e inicia-lo em um mambiente controlado, também vimos que a ideia do container é que seja efêmero. Então como fazer com que os containers salvem os dados gerados? A repsosta para essa pergunta é **utilizando volumes!** 16 | 17 | Ao criar um volume, os dados que estão nele serão montados em algum diretório especifico dentro do container, os volumes são salvos na máquina real, desta forma, garantimos a opersistencia dos dados já que eles ficam idepedente do container. 18 | 19 | ## Tipos de volumes 20 | Existe dois tipos de volume: com bind e sem bind, volumes com bind são utilizados quando você quer especificar o caminho da sua máquina onde estão os dados do container enquanto volumes sem bind esse trabalho é feito pelo próprio Docker. 21 | 22 | A criação do volume pode ser feito na criação do container utilizando o paramêtro ``-v`` 23 | 24 | ``` 25 | # Exemplo de criação de volume com bind 26 | -v /home/user/container_data:/opt/app 27 | ``` 28 | 29 | ``` 30 | # Exemplo de criação de volume sem bind 31 | -v /opt/app (volume sem bind) 32 | ``` 33 | 34 | Normamelnte volumes com bind são utilizados quando é preciso **fazer modificações** nos dados frequentemente, ao alterar os dados na pasta da sua máquina real em que está fazendo o bind, os dados dentro do container também serão alterados. **Arquivos de configuração ou códigos de desenvolvimento** são exemplos de bons usos dese tipo de volume. 35 | 36 | Já os volume sem bind geralmente são utilizados quando queremos **apenas salvar as informações** e queremos apenas ler esses dados, **banco de dados** é um exemplo que pode utilizar esse tipo de volme. 37 | 38 | ## Deixando BD persistente 39 | 40 | Para que possamos deixar os dados do banco de dados persistene primeiro precisamos saber como que o seu sgbd armazena os dados. No nosso caso que estamos utilizando mysql, ele armazena os dados no caminho ``/var/lib/mysql``, então o que será necessário é apenas informar ao Docker que essa pasta será armazenada em um volume. No nosso exemplo será: 41 | 42 | 43 | ``` 44 | docker run -d --name bd -e MYSQL_ROOT_PASSWORD=senha -v my_data:/var/lib/mysql mysql:latest 45 | ``` 46 | Assim como com o parametro ``-p`` o parametro ``-v`` é dividido em duas partes pelo dois pontos. Em um volume sem bind que criamos agora a palavra antes dos dois pontos **my_data** é o nome do volume que estamos criando e a palavra depois dos dois pontos é o caminho que está dentro do container que queremos aramazenar. 47 | 48 | Execute o comando ``docker volume ls`` e verifique que existe um volume chamado my_data. 49 | 50 | ``` 51 | DRIVER VOLUME NAME 52 | local my_data 53 | ``` 54 | 55 | ## Criando container para desenvolvimento 56 | 57 | Assim como vimos que a melhor maneira de criar um volume para desenvolvimento é utilizando bind, vamos criar o container da nossa aplicação novamente da seguinte forma: 58 | 59 | ``` 60 | docker run -d --name my_app -p 1915:3000 -v "$PWD"/app:/opt/ --link bd:my_bd my_node:1 61 | ``` 62 | 63 | Acessem http://localhost:1915 para verificar que a aplicação está acessivel. 64 | 65 | Em seguinda edite o arquivo `lvl.4.volumes/app/index.html` localize o nome ``Make Anything with Docker.`` e subsituta para ``Make My Web App with Docker.`` 66 | 67 | Atualize a página http://localhost:1915 e verifque as alterações, isso só foi possivél porque o arquivo dentro do container também foi modificado. Desta forma é possivel criar um ambiente docker para o desenvolvimento e não terá mais necessidade de configurar cada máquina para desenvolver a aplicação, basta ter Docker! 68 | 69 | #### [Próximo -> Lvl 5 - Docker Compose](http://github.com/victorhundo/docker-ceres/tree/master/lvl.5.compose) 70 | -------------------------------------------------------------------------------- /lvl.4.volumes/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | LABEL maintainer "Victor Hugo " 3 | 4 | WORKDIR /opt 5 | 6 | RUN apt-get update && apt-get install -y \ 7 | nmap \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN npm install -g nodemon 11 | 12 | EXPOSE 3000 13 | 14 | COPY ./* /opt/ 15 | 16 | CMD [ "bash", "/opt/start.sh"] 17 | -------------------------------------------------------------------------------- /lvl.4.volumes/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Docker World! 9 | 10 | 11 | 12 | 13 | 14 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |

Hello Docker World

186 | 193 |
194 |
195 |
196 | 198 |

Make Anything with Docker.

199 |
200 |
201 |
202 |

by @victorhundo.

203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lvl.4.volumes/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-hello-world", 3 | "description": "Servidor web para oficina de Docker", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache Version 2.0", 7 | "author": "Victor Hugo", 8 | "scrips": { 9 | "start": "node index.js" 10 | }, 11 | "engines": { 12 | "node": ">=4.3.2" 13 | }, 14 | "dependencies": { 15 | "express": "^4.14.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lvl.4.volumes/app/server.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | var express = require('express'); 3 | var app = express(); 4 | const PORT = process.env.PORT || 3000; 5 | app.get('/', function (req, res) { 6 | res.sendfile('./index.html'); 7 | }); 8 | 9 | // Start the server 10 | app.listen(PORT, () => { 11 | console.log(`App listening on port ${PORT}`); 12 | console.log('Press Ctrl+C to quit.'); 13 | }); 14 | -------------------------------------------------------------------------------- /lvl.4.volumes/app/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | npm start 5 | -------------------------------------------------------------------------------- /lvl.5.compose/README.md: -------------------------------------------------------------------------------- 1 | # Docker Compose 2 | 3 | ![Banner Docker](../assets/docker-banner-5.jpg) 4 | 5 | Como vocês puderam reparar, os comandos do docker só fazem crescer e fica ainda mais complexo quando precisamos utilizar mais de um container para fazer nosso serviço funcionar (é natural que mais de 3 containers trabalhem em conjunto para uma única aplicação). 6 | 7 | O Docker Compose veio para **simplificar e facilitar** a manutenção de múltiplos containers para uma unica aplicação. 8 | 9 | ## docker-compose.yaml 10 | 11 | O docker compose utiliza um arquivo no padrão yaml para poder configurar a criação dos containers. Dentro do arquivo ``/lvl.5.compose/app/`` existe um arquivo chamado **docker-compose.yaml**, vamos analisar as informações que estão neles, assim como foi feito no arquivo Dockerfile. 12 | 13 | ### version 14 | 15 | ``` 16 | version: '3' 17 | ``` 18 | Indica a versão que o arquivo do docker-compose.yaml vai criar os containers, se for utilizar outra versão, parametros diferentes será necessário. A versão 3 é a mais recente. 19 | 20 | ### services 21 | ``` 22 | services: 23 | ``` 24 | 25 | No docker compose, cada container é conhecido como serviço, no nosso caso, vamos iniciar 2: web e bd. 26 | 27 | ### image 28 | 29 | Reparem que no serviço de web temos a nossa imagem "myapp", caso ela não exista na máquina, será criada pelo Dockerfile que está no repositório, o docker-compose sabe disso por causa do parametro ``build: .`` o "ponto" signifca que o Dockerfile está no mesmo diretorio que o arquivo docker-compose.yaml 30 | 31 | No serviço do BD, não tem o parametro build, logo, se não tiver a imagem na sua máquina, ela será baixada pelo Docker Hub. 32 | 33 | ### container_name 34 | 35 | O nome do container é criado nesse parametro. 36 | 37 | ### volumes 38 | 39 | O volume com bind é feito no serviço web, da mesma forma com que é foi feito no módulo anterior, diferença apenas que não precisa dizer o path completo. 40 | 41 | O volume sem bind no serviço do bd, é descrito qual volume será utilizado. Nesse caso, o volume é descrito depois de "services". 42 | 43 | 44 | ## Criando vários containers ao mesmo tempo 45 | 46 | Para criar os containers, basta entrar na pasta ``/lvl.5.compose/app`` e executar o comando 47 | 48 | ``` 49 | docker-compose up 50 | ``` 51 | 52 | ## Removendo vários containers ao mesmo tempo 53 | 54 | Para remover os containers basta executar 55 | 56 | ``` 57 | docker-compose down 58 | ``` 59 | -------------------------------------------------------------------------------- /lvl.5.compose/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | LABEL maintainer "Victor Hugo " 3 | 4 | WORKDIR /opt 5 | 6 | RUN apt-get update && apt-get install -y \ 7 | nmap \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN npm install -g nodemon 11 | 12 | EXPOSE 3000 13 | 14 | COPY ./* /opt/ 15 | 16 | CMD [ "bash", "/opt/start.sh"] 17 | -------------------------------------------------------------------------------- /lvl.5.compose/app/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | image: my_node:1 5 | build: . 6 | container_name: myWeb-app 7 | links: 8 | - bd:my_bd 9 | volumes: 10 | - ./:/opt/ 11 | ports: 12 | - "1915:3000" 13 | 14 | bd: 15 | image: mysql:latest 16 | container_name: bd 17 | restart: always 18 | environment: 19 | - MYSQL_ROOT_PASSWORD=senha 20 | volumes: 21 | - datavolume:/var/lib/mysql 22 | 23 | volumes: 24 | datavolume: 25 | -------------------------------------------------------------------------------- /lvl.5.compose/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Docker World! 9 | 10 | 11 | 12 | 13 | 14 | 179 | 180 |
181 |
182 |
183 |
184 |
185 |

Hello Docker World

186 | 193 |
194 |
195 |
196 | 198 |

Make Anything with Docker.

199 |
200 |
201 |
202 |

by @victorhundo.

203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lvl.5.compose/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-hello-world", 3 | "description": "Servidor web para oficina de Docker", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache Version 2.0", 7 | "author": "Victor Hugo", 8 | "scrips": { 9 | "start": "node index.js" 10 | }, 11 | "engines": { 12 | "node": ">=4.3.2" 13 | }, 14 | "dependencies": { 15 | "express": "^4.14.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lvl.5.compose/app/server.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | var express = require('express'); 3 | var app = express(); 4 | const PORT = process.env.PORT || 3000; 5 | app.get('/', function (req, res) { 6 | res.sendfile('./index.html'); 7 | }); 8 | 9 | // Start the server 10 | app.listen(PORT, () => { 11 | console.log(`App listening on port ${PORT}`); 12 | console.log('Press Ctrl+C to quit.'); 13 | }); 14 | -------------------------------------------------------------------------------- /lvl.5.compose/app/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | npm start 5 | --------------------------------------------------------------------------------