docker run nome_imagem
docker ps
docker ps -a
docker run -it nome_imagem
docker start id_do_container
docker stop id_do_container
docker rm id_do_container
docker container prune
docker images
docker rmi nome_container
docker run -d nome_container
docker run -d -P nome_container
docker port id_container
docker run -d -P --name meu-site nome_container
docker run -d -p 12345:80 nome_container
docker run -d -P -e AUTHOR="Paulo Neto" dockersamples/static-site
docker stop $(docker ps -q)
Toda imagem que baixamos é composta de uma ou mais camadas, e esse sistema tem o nome de Layered File System. Essas camadas podem ser reaproveitadas em outras imagens. Por exemplo, se já temos a imagem do Ubuntu, isso inclui as suas camadas, e agora queremos baixar a imagem do CentOS. Se o CentOS compartilha alguma camada que já tem na imagem do Ubuntu, o Docker é inteligente e só baixará as camadas diferentes, e não baixará novamente as camadas que já temos no nosso computador: Uma outra vantagem é que as camadas de uma imagem são somente para leitura. Mas como então conseguimos criar arquivos na aula anterior? O que acontece é que não escrevemos na imagem, já que quando criamos um container, ele cria uma nova camada acima da imagem, e nessa camada podemos ler e escrever:
DevOps é um tópico bem amplo que envolve tanto aspectos culturais como técnicos, mas possui como principal objetivo aumentar a qualidade e eficiência da entrega de software. DevOps é uma metodologia que visa integrar os times de desenvolvimento com infraestrutura e o Docker está tendo um papel importante nessa tarefa.
Repare que com Docker os desenvolvedores não precisam se preocupar em configurar um ambiente de desenvolvimento específico de cada vez. Em vez disso, eles podem se concentrar na construção de um código de boa qualidade. Isso, obviamente, leva à aceleração nos esforços de desenvolvimento. O Docker facilita muito construtir o ambiente: é rapido, simples e confiável.
No outro lado, para a equipe de operações de TI / Sysadmins, o Docker possibilita configurar ambientes que são exatamente como um servidor de produção e permite que qualquer pessoa trabalhe no mesmo projeto com exatamente as mesmas configurações, independentemente do ambiente de host local. As configurações são descritas em arquivos simples facilmente aplicáveis pelo desenvolvedor.
Com a padronização de um entregável Docker é possível que o desenvolvedor tenha um ambiente similar ao de produção na sua máquina sem todo o custo de configuração e o sysadmin consiga lidar apenas com um tipo de entregável conseguindo, desta forma, dar atenção aos desafios de monitoramento e orquestração para que nada dê errado. Neste caso o melhor para os dois.
Quando escrevemos em um container, assim que ele for removido, os dados também serão. Mas podemos criar um local especial dentro dele, e especificamos que esse local será o nosso volume de dados. Quando criamos um volume de dados, o que estamos fazendo é apontá-lo para uma pequena pasta no Docker Host. Então, quando criamos um volume, criamos uma pasta dentro do container, e o que escrevermos dentro dessa pasta na verdade estaremos escrevendo do Docker Host. Isso faz com que não percamos os nossos dados, pois o container até pode ser removido, mas a pasta no Docker Host ficará intacta.
docker run -v "/var/www" ubuntu
docker inspect id_container
docker run -it -v "/home/paulo-neto/Desktop:/var/www" nome_container
Já vimos que o que escrevemos no volume (pasta /var/www do container) aparece na pasta configurada da nossa máquina local, que no vídeo anterior foi o Desktop. Mas podemos pensar o contrário, ou seja, tudo o que escrevemos no Desktop será acessível na pasta /var/www do container. Isso nos dá a possibilidade de implementar localmente um código de uma linguagem que não está instalada na nossa máquina, e colocá-lo para compilar e rodar dentro do container. Se o container possui Node, Java, PHP, seja qual for a linguagem, não precisamos tê-los instalados na nossa máquina, nosso ambiente de desenvolvimento pode ser dentro do container.
docker run -p 8080:3000 -v "C:\Users\Alura\Desktop\volume-exemplo:/var/www" -w "/var/www" node npm start
docker run -p 8080:3000 -v "$(pwd):/var/www" -w "/var/www" node npm start
Geralmente, montamos as nossas imagens a partir de uma imagem já existente. Nós podemos criar uma imagem do zero, mas a prática de utilizar uma imagem como base e adicionar nela o que quisermos é mais comum. Para dizer a imagem-base que queremos, utilizamos a palavra FROM mais o nome da imagem.
FROM node:latest
FROM node:latest
MAINTAINER Paulo Neto
FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
RUN npm install
FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
RUN npm install
ENTRYPOINT npm start
FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
WORKDIR /var/www
RUN npm install
ENTRYPOINT npm start
EXPOSE 3000
Para criar a imagem, precisamos fazer o seu build através do comando docker build
, comando utilizado para buildar
uma imagem a partir de um Dockerfile
. Para configurar esse comando, passamos o nome do Dockerfile através da flag -f
. Dockerfile é o padrão, poderíamos omitir esse parâmetro, mas se o nome for diferente, por exemplo node.dockerfile, é preciso especificar.
passar a tag da imagem, o seu nome, através da flag -t
, para imagens não-oficiais colocamos o nome no padrão
docker build -f Dockerfile -t pauloneto/node
indicar onde o docker file está. Como já estamos rodando o comando dentro da pasta utilizamos um ponto (.)
docker build -f Dockerfile -t pauloneto/node .
docker run -d -p 8080:3000 douglasq/node
no Dockerfile, também podemos criar variáveis de ambiente, utilizando o ENV. Por exemplo, para criar a variável PORT, para dizer em que porta a nossa aplicação irá rodar, fazemos:
FROM node:latest
MAINTAINER Douglas Quintanilha
ENV PORT=3000
COPY . /var/www
WORKDIR /var/www
RUN npm install
ENTRYPOINT npm start
EXPOSE $PORT
O primeiro passo é criar a nossa conta. Com ela criada, no terminal nós executamos o comando docker login
e digitamos o nosso login e senha que acabamos de criar.
Após isso, basta executar o comando docker push
, passando para ele a imagem que queremos subir, por exemplo:
docker push pauloneto/node
para baixar uma imagem podemos utilizar o comando docker pull: docker pull pauloneto/node
No Docker, por padrão, já existe uma default network. Isso significa que, quando criamos os nossos containers, por padrão eles funcionam na mesma rede.
hostname -i
apt-get update && apt-get install iputils-ping
O Docker criar uma rede virtual, em que todos os containers fazem parte dela, com os IPs automaticamente atribuídos. Mas quando os IPs são atribuídos, cada hora em que subirmos um container, ele irá receber um IP novo, que será determinado pelo Docker. Logo, se não sabemos qual o IP que será atribuído, isso não é muito útil quando queremos fazer a comunicação entre os containers. Por exemplo, podemos querer colocar dentro do aplicativo o endereço exato do banco de dados, e para saber exatamente o endereço do banco de dados, devemos configurar um nome para aquele container. Mas nomear um container nós já sabemos, basta adicionar o --name, passando o nome que queremos na hora da criação do container, certo? Apesar de conseguirmos dar um nome a um container, a rede do Docker não permite com que atribuamos um hostname a um container, diferentemente de quando criamos a nossa própria rede. Na rede padrão do Docker, só podemos realizar a comunicação utilizando IPs, mas se criarmos a nossa própria rede, podemos "batizar" os nossos containers, e realizar a comunicação entre eles utilizando os seus nomes:
Isso não pode ser feito na rede padrão do Docker, somente quando criamos a nossa própria rede.
Podemos criar a nossa própria rede, através do comando docker network create
, mas não é só isso, para esse comando também precisamos dizer qual driver vamos utilizar. Para o padrão que vimos, de ter uma nuvem e os containers compartilhando a rede, devemos utilizar o driver de bridge
.
Especificamos o driver através do --driver
e após isso nós dizemos o nome da rede. Um exemplo do comando é o seguinte:
docker network create --driver bridge minha-rede
Agora, quando criamos um container, ao invés de deixarmos ele ser associado à rede padrão do Docker, atrelamos à rede que acabamos de criar, através da flag --network
. Vamos aproveitar e nomear o container:
docker run -it --name meu-container-de-ubuntu --network minha-rede ubuntu
Assim conseguimos realizar a comunicação entre os containers utilizando somente os seus nomes. É como se o Docker Host, o ambiente que está rodando os containers, criasse uma rede local chamada minha-rede, e o nome do container será utilizado como se fosse um hostname, lembrando que só conseguimos fazer isso em redes próprias, redes que criamos, isso não é possível na rede padrão dos containers.
Para baixar a imagem ubuntu do Docker Hub você pode usar docker pull ubuntu
. Isso é diferente do comando run
, que baixa a imagem (se não existe local) e depois criar e roda o container. O pull apenas baixa!
Para baixar uma imagem de um usuário especifico existe a sintaxe: docker pull NOME_USUARIO/NOME_IMAGEM
Além disso, uma imagem pode ter um tag que serve para pegar uma determinada versão dessa imagem. Existe a tag :latest
e uma tag especifica :nome_tag
, por exemplo: docker pull pauloneto/nome_imagem:nome_tag
Se formos seguir esse diagrama, teríamos que criar cinco containers na mão, e claro, cada container com configurações e flags diferentes, além de termos que nos preocupar com a ordem em que vamos subi-los.
Ao invés de subir todos esses containers na mão, o que vamos fazer é utilizar uma tecnologia aliada do Docker, chamada Docker Compose, feito para nos auxiliar a orquestrar melhor múltiplos containers. Ele funciona seguindo um arquivo de texto YAML (extensão .yml), e nele nós descrevemos tudo o que queremos que aconteça para subir a nossa aplicação, todo o nosso processo de build, isto é, subir o banco, os containers das aplicações, etc. Assim, não precisamos ficar executando muitos comandos no terminal sem necessidade.
Para utilizar o Docker Compose, devemos criar o seu arquivo de configuração, o docker-compose.yml, na raiz do projeto. Em todo arquivo de Docker Compose, que é uma espécie de receita de bolo para construirmos as diferentes partes da nossa aplicação, a primeira coisa que colocamos nele é a versão do Docker Compose que estamos utilizando: version:'3'
O YAML lembra um pouco o JSON, mas ao invés de utilizar as chaves para indentar o código, ele utiliza espaços.
Agora, começamos a descrever os nossos serviços, os nossos services:
version: '3'
services:
Seguindo a figura 02
uma aplicação tem vários serviços.
Temos NGINX, três Node, e o MongoDB como serviços. Logo, se queremos construir cinco containers, vamos construir cinco serviços, cada um deles com um nome específico. Então, vamos começar construindo o NGINX, que terá o nome nginx:
version: '3'
services:
nginx:
Em cada serviço, devemos dizer como devemos construí-lo, como devemos fazer o seu build:
version: '3'
services:
nginx:
build:
O serviço será construído através de um Dockerfile, então devemos passá-lo onde ele está. E também devemos passar um contexto, para dizermos a partir de onde o Dockerfile deve ser buscado. Como ele será buscado a partir da pasta atual, vamos utilizar o ponto:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
Construída a imagem, devemos dar um nome para ela, por exemplo pauloneto/nginx:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
E quando o Docker Compose criar um container a partir dessa imagem, vamos dizer que o seu nome será nginx:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
Sabemos também que o NGINX trabalha com duas portas, a 80 e a 443. Como não estamos trabalhando com HTTPS, vamos utilizar somente a porta 80, e no próprio arquivo, podemos dizer para qual porta da nossa máquina queremos mapear a porta 80 do container. Vamos mapear para a porta de mesmo número da nossa máquina:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
No YAML, toda vez que colocamos um traço, significa que a propriedade pode receber mais de um item. Agora, para os containers conseguirem se comunicar, eles devem estar na mesma rede, então vamos configurar isso também. Primeiramente, devemos criar a rede, que não é um serviço, então vamos escrever do começo do arquivo, sem as tabulações:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
O nome da rede será production-network e utilizará o driver bridge:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
production-network:
driver: bridge
Com a rede criada, vamos utilizá-la no serviço:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
- production-network
networks:
production-network:
driver: bridge
Isso é para construir o serviço do NGINX, agora vamos construir o serviço do MongoDB, com o nome mongodb. Como ele será construído a partir da imagem mongo, não vamos utilizar nenhum Dockerfile, logo não utilizamos a propriedade build. Além disso, não podemos nos esquecer de colocá-lo na rede que criamos:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
- production-network
mongodb:
image: mongo
networks:
- production-network
networks:
production-network:
driver: bridge
Falta agora criarmos os três serviços em que ficará a nossa aplicação, node1, node2 e node3. Para eles, será semelhante ao NGINX, com Dockerfile alura-books.dockerfile, contexto, rede production-network e porta 3000:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
- production-network
mongodb:
image: mongo
networks:
- production-network
node1:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-1
ports:
- "3000"
networks:
- production-network
node2:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-2
ports:
- "3000"
networks:
- production-network
node3:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-3
ports:
- "3000"
networks:
- production-network
networks:
production-network:
driver: bridge
Por último, quando subimos os containers na mão, temos uma ordem, primeiro devemos subir o mongodb, depois a nossa aplicação, ou seja, node1, node2 e node3 e após tudo isso subimos o nginx. Mas como que fazemos isso no docker-compose.yml? Nós podemos dizer que os serviços da nossa aplicação dependem que um serviço suba antes deles, o serviço do mongodb:
version: '3'
services:
nginx:
build:
dockerfile: ./docker/nginx.dockerfile
context: .
image: pauloneto/nginx
container_name: nginx
ports:
- "80:80"
networks:
- production-network
depends_on:
- "node1"
- "node2"
- "node3"
mongodb:
image: mongo
networks:
- production-network
node1:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-1
ports:
- "3000"
networks:
- production-network
depends_on:
- "mongodb"
node2:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-2
ports:
- "3000"
networks:
- production-network
depends_on:
- "mongodb"
node3:
build:
dockerfile: ./docker/alura-books.dockerfile
context: .
image: pauloneto/alura-books
container_name: alura-books-3
ports:
- "3000"
networks:
- production-network
depends_on:
- "mongodb"
networks:
production-network:
driver: bridge
Garantir que temos todas as imagens envolvidas neste arquivo na nossa máquina.
docker-compose build
Com os serviços criados, podemos subi-los através do comando docker-compose up. Esse comando irá seguir o que escrevemos no docker-compose.yml, ou seja, cria a rede, o container do MongoDB, os três containers da aplicação e o container do NGINX. Depois, são exibidos alguns logs, sendo que cada um dos containers fica com uma cor diferente, para podermos distinguir melhor.
Para parar a execução, utilizamos o atalho CTRL + C. E não somos obrigados a ficar vendo esses logs, podemos utilizar a já conhecida flag -d:
docker-compose up -d
E com o comando docker-compose ps
, podemos ter uma visualização simples dos serviços que estão rodando:
Agora que não estamos mais vendo os logs, como paramos os serviços? Para isso, utilizamos o comando docker-compose down
. Esse comando para os containers e os remove.
E não é por que eles são serviços, que eles não tem um container por debaixo dos panos, então nós conseguimos interagir com os containers utilizando todos os comandos que já vimos no treinamento, por exemplo para testar a comunicação entre eles:
docker exec -it alura-books-1 ping alura-books-2
Mas também podemos utilizar o nome do serviço, não precisamos necessariamente utilizar o nome do container:
docker exec -it alura-books-1 ping node2
O Docker Compose não é instalado por padrão no Linux, então você deve instalá-lo por fora. Para tal, baixe-o na sua versão mais atual, que pode ser visualizada no seu GitHub, executando o comando abaixo:
sudo curl -L https://github.com/docker/compose/releases/download/1.15.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
Após isso, dê permissão de execução para o docker-compose:
sudo chmod +x /usr/local/bin/docker-compose