Docker 101 - Primeiros passos e muito mais!

Docker é uma plataforma Open Source escrita em Go (Linguagem de programação em alta performance desenvolvida pela Google) que ajuda a criação e a administração de ambientes isolados.
Um container consiste de um ambiente completo (uma aplicação e todas suas dependências, bibliotecas, binários, arquivos de configuração) em um unico pacote. Ao containerizar uma plataforma de aplicação e suas dependências as diferenças em distribuições de sistemas operacionais e camadas inferiores da infraestrutura são abstraídas.
Imagine que o container Docker é como se fosse um container real em um navio (servidor), todos os containeres estão lado a lado, porém seu conteúdo (ecossistema) não tem interferencia de outros containeres
Não iremos explicar quando utilizar Containers e a base de como um conteiner funciona, para isto recomendo que você leia o post Containers ou Máquinas Virtuais - Quando usar cada um?
Criando a Infraestrutura do nosso laboratório
Podemos utilizar o Docker instalado diretamente em nossa máquina, mas para manter o padrão dos outros laboratórios do blog, iremos utilizar um ubuntu server através do vagrant, você pode utilizar qualquer máquina virtual que quiser ou instalar diretamente em sua máquina, basta seguir para a próxima seção caso não queira utilizar o vagrant.
A idéia é quem passar por todos os posts ter um laboratório real de Infraestrutura como Código.
Caso você queira conhecer o vagrant sugiro que leia o post Vagrant 101 do blog. Você pode verificar todos os posts introdutórios através da tag 101
Vagrantfile para o Docker
Vamos criar um diretório para armazenar o conteúdo do Vagrantfile e popula-lo com as configurações para subir o ambiente.
Criaremos no momento apenas a máquina docker01 em um post futuro iremos subir um cluster com mais máquinas.
$ mkdir ~/docker
$ cd ~/docker
$ vim Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box_check_update = false
config.vm.boot_timeout = 600
config.vm.define "docker01" do |machine|
machine.vm.box = "ubuntu/bionic64"
machine.vm.hostname = "docker01.caiodelgado.example"
machine.vm.network "private_network", ip: "10.10.10.20"
machine.vm.provider "virtualbox" do |vb|
vb.name = "docker01"
vb.memory = "1024"
vb.cpus = "1"
vb.customize ["modifyvm", :id, "--groups", "/iac"]
end
end
end
$ vagrant up
Adicione a entrada do docker01 no arquivo hosts para que o acesso ao mesmo fique mais fácil.
$ sudo sh -c "echo '10.10.10.20 docker01.caiodelgado.example' >> /etc/hosts"
Vamos acessar o servidor para instalar o Docker.
$ cd ~/docker
$ vagrant ssh
Instalando o Docker
Existem duas maneiras de instalar o Docker
- Script de Conveniência
- Maneira Tradicional
Para nosso laboratório você pode escolher qualquer uma das duas maneiras.
Script de Conveniência
ATENÇÃO: A utilização do script de Conveniência não é recomendado para ambientes de produção.
A maneira mais simples de instalar docker é utilizando o script de conveniência, este script é responsável por validar sua distribuição Linux e instalar os pacotes necessários para o funcionamento do Docker
$ sudo curl -fsSL https://get.docker.com | bash
Depois disto podemos adicionar nosso usuário ao grupo docker
, isso vai garantir que possamos rodar os comandos com nosso usuário
$ sudo gpasswd -a $USER docker
Lembre-se que isto só funcionará após a regarga das varíaveis de ambiente, para isto faça logout e login novamente.
Maneira Tradicional
Irei cobrir a instalação tradicional para o sistema Ubuntu, para outras distribuições verifique a Documentação Oficial
$ sudo apt update
$ sudo apt install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common \
bash-completion \
jq -y
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
$ sudo apt update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
Depois disto podemos adicionar nosso usuário ao grupo docker
, isso vai garantir que possamos rodar os comandos com nosso usuário
$ sudo gpasswd -a $USER docker
Lembre-se que isto só funcionará após a regarga das varíaveis de ambiente, para isto faça logout e login novamente.
Bash Completion
Podemos também instalar o recurso de Bash Completion através do comando:
$ sudo curl https://raw.githubusercontent.com/docker/machine/v0.16.0/contrib/completion/bash/docker-machine.bash -o /etc/bash_completion.d/docker-machine
O que é um Container Docker?
Um container docker é um processo em área restrita que executa um aplicativo e suas dependências no sistema operacional hospedeiro. Os recursos do sistema operacional(como o kernel) são compartilhados com os containers os quais utilizam apenas os recursos necessários para seu processo ser executado.

Executando nosso primeiro container Docker
Para garantir que nosso usuário tem as permissões de executar o docker, vamos sair do terminal e conectar novamente via ssh
$ exit
$ vagrant ssh
Agora podemos testar a execução de um container através do comando docker container run --rm -it hello-world

Agora precisamos entender os parâmetros utilizados.
-
docker container run
- Informa para o docker que queremos executar um container --rm
- Informa para o docker que queremos remover o container após ele cumprir seu papel.-it
ou-i -t
- Informa para o docker executar o container em modo interativo-i
e com um pseudoTTY-t
, com isso podemos iteragir com o terminal e ver seus logs diretos em nossostdout
estderr
hello-world
- Informa ao docker qual imagem de container gostariamos de executar.
Componentes do Docker
Agora que rodamos nosso primeiro container, precisamos entender alguns componentes básicos da sua arquitetura e seu funcionamento.
Ao executar o container com a imagem hello-world
o Docker fez os seguintes passos:
- O
Docker Client
se comunicou com oDocker Daemon
- O
Docker Daemon
fez o download da imagemhello-world
no Docker Hub - O
Docker Daemon
criou um novo container, através da imagem que rodou o executável que produz o texto que vimos no terminal. - O
Docker Daemon
enviou o texto diretamente para oDocker Client
que enviou para nosso terminal.

Imagem
Uma imagem Docker é um pacote executável que inclui tudo o que é necessário para executar um aplicativo, incluindo o código, bibliotecas, variáveis de ambientes e arquivos de configuração.
As imagens do Docker possuem camadas intermediárias que aumentam a capacidade de reutilização, diminuem o uso do disco e aceleram a construção do docker, permitindo que cada etapa seja armazenada em cache. Essas camadas intermediárias não são mostradas por padrão.

A principal diferença entre um container e uma imagem é a camada gravável superior. Todas as gravações no container que adicionam novos dados ou modificam dados existentes são armazenados nessa camada gravável. Quando o container é excluido, a camada gravável também é excluida. A imagem subjacente permanece inalterada.

Um ponto interessante é que em um cenário onde temos 1 container com a imagem ubuntu:18.04
por exemplo, ocuparia 200MB
( estamos considerando este tamanho para a imagem citada) somados a quantidade de dados específicos deste container ( vamos considerar 50MB
para este exemplo) totalizando 250MB
. o mesmo caso com 10 containers serão utilizados os 200MB
da imagem somados aos 50MB
de cada container em execução, pois suas camadas readonly
é compartilhada, totalizando assim 750MB
no total.
O mesmo cenário em máquinas virtuais seria exponencialmente maior, uma vez que precisamos instalar todo o sistema operacional e parametrizar cada máquina individualmente.
DockerHub
O DockerHub é um repositório publico de imagens docker onde podemos escolher e utilizar as imagens para subir nossos containers.

Ao selecionar uma imagem temos diversas informações inclusive como fazer o download e utilizar a mesma.
Dockerfile
O Dockerfile é um arquivo de instruções para como deve ser gerada a imagem Docker, através deste arquivo podemos criar novas imagens para serem utilizadas.
Vamos criar um diretório e o arquivo Dockerfile
para criar nossa primeira imagem docker.
É importante que o arquivo seja chamado Dockerfile
(case sensitive) ou então não irá funcionar a imagem
$ mkdir -p /vagrant/dockerfiles/echontainer
$ cd /vagrant/dockerfiles/echontainer
$ vim Dockerfile
FROM alpine
ENTRYPOINT ["echo"]
CMD ["--help"]

Entendendo nosso Dockerfile:
FROM alpine
- Informa que utilizaremos a imagem alpine como base.ENTRYPOINT ["echo"]
- Informa que o ponto de entrada do container será o comando echo, esteentrypoint
é responsável por manter o container vivo, caso ele retorne algum erro o container é parado.CMD ["--help"]
- Informa parâmetros adicionais ao entrypoint.
Em um post futuro exploraremos mais sobre construção de imagens, por hora focaremos no básico.
Para construir a imagem podemos utilizar o comando docker image build
$ cd /vagrant/dockerfiles/echontainer/
$ docker image build -t echontainer .

Entendendo o comando:
docker image build
- Informa ao docker que queremos construir uma imagem.-t
- Informa o nome e a tag da imagem no formatonome:tag
, no nosso casoechontainer:latest
.
- Informa que o context de construção da imagem é o diretório atual.
Caso não seja informada a tag da imagem o docker entende que sua tag é a latest
Podemos agora listar as imagens no repositório local da nossa máquina
$ docker image ls

Agora que temos nossa imagem echontainer
podemos executar nosso container
$ docker container run -it echontainer

Perceba que nosso container foi executado, como resultado tivemos a execução do entrypoint
do container com os parâmetros do cmd
no caso executando o comando echo --help
Podemos verificar que o container foi executado e parou em sequencia através do comando docker container ls -a
O comandodocker container ls
lista apenas os containers ativos , o comandodocker container ls -a
lista também os containers não ativos.

Uma curiosidade é que nosso container recebeu o nome dependantic_bohr
, o nome de todos os containers que não são informados para serem criados com um nome através do parâmetro--name
é gerado da formaadjetivo
+_
+sobrenome
de um cientista ou hackers, lista completa no repositório oficial do projeto moby
Podemos também alterar o cmd
de um container diretamente na execução, passando após a declaração da imagem o parâmetro que gostariamos de utilizar, sendo assim a execução seria entrypoint
+ parâmetro customizado.
$ docker container run -it echontainer "Estou lendo o blog do Caio em https://caiodelgado.dev"

Ao listar os containers através do docker container ls -a
podemos verificar que o COMMAND
do container executado foi modificado.

Vamos criar um novo Dockerfile
para que possamos criar a imagem que utilizaremos no próximo tópico
$ mkdir -p /vagrant/dockerfiles/lighttpd
$ cd /vagrant/dockerfiles/lighttpd
$ vim Dockerfile
FROM alpine
RUN apk add --no-cache lighttpd
RUN echo "Lighttpd Web Server is Running" > /var/www/localhost/htdocs/index.html
EXPOSE 80
CMD lighttpd -D -f /etc/lighttpd/lighttpd.conf

O parâmetroRUN
faz com que seja executado um comando na criação da imagem, o parâmetroEXPOSE
informa ao docker qual porta deve ser exposta ao criar um container com esteDockerfile
Lembrando que não estamos preocupados no momento com a qualidade da criação da imagem, isto ficará para um próximo post, estamos focando apenas no básico.
Vamos criar a imagem agora
$ cd /vagrant/lighttpd/lighttpd.conf
$ docker image build -t lighttpd .

Network
O Recurso de rede é utilizado para expor a aplicação do container para a rede da máquina hospedeira.
Existem diversas funcionalidades e tipos de redes, para este tutorial iremos abordar apenas a publicação de portas do container para a máquina hospedeira. Mais informações podem ser encontradas na Documentação Oficial.
Trabalharemos com a rede do tipo host, onde é criada uma interface virtual dentro de nossa máquina e a mesma é roteada através dos recursos e regras de rede como iptables
para o container.
Assim que instalamos o docker, por padrão é criada uma interface bridge chamada de docker 0
, podemos lista-la através do comando ip -c a show docker0

Note que a interfacedocker0
encontra-se em estadoDOWN
Para expor a aplicação do container podemos utilizar o parâmetro -p
ou --publish
seguido da porta da máquina hospedeira e da porta aplicação do container.
Vamos executar nosso container web
$ docker container run -d --name websrv -p 80:80 lighttpd
Entendendo os parâmetros
-d
- Detached , faz com que executamos o container de forma "desanexada"-p 80:80
- Cria uma série de regras de encaminhamento para acessar o container através da porta 80 da máquina hospedeira.
Em seguida verifique os status da interface docker0
que agora estará como UP
.

Acesse agora o endereço http://docker01.caiodelgado.example
em seu navegador e verifique que nosso webserver está sendo executado.

Podemos também verificar via terminal através do comando curl localhost
na máquina docker01
ou curl docker01.caiodelgado.example
na nossa máquina local.


Vamos agora remover os containers criados anteriormente, podemos utilizar o comando docker container ls -aq
para listar apenas os containers por id
e passando isto como subshell para o comando docker container rm -f
podemos forçar a remoção de todos os containers que estão rodando ou parados em nossa máquina.
$ docker container ls -aq
$ docker container rm -f $(docker container ls -aq)
O subshell pode ser executado através do comando$(comando)
, sendo assim é executado como se fosse uma operação matemática como por exemplo2 x (1+3)
, os parênteses são resolvidos primeiramente e depois o externo, logo no nosso comando executado é como se estivessemos executandodocker container rm -f <id1> <id2> <id3>
.

Volumes
Volumes são recursos utilizados para transportar arquivos e diretórios para dentro de um container.
Existem diversas funcionalidades e tipos de volumes, iremos abordar apenas a montagem de diretórios/arquivos dentro de um container. Mais informações podem ser encontradas na Documentação Oficial.
Vamos criar o arquivo index.html
para substituirmos o index da imagem lighttpd
no momento que executarmos o webserver.
$ cd /vagrant/dockerfiles/lighttpd/
$ cat > index.html <<EOF
<meta charset="UTF-8">
<h1> Servidor Lighttpd</h1>
<p> Esta aplicação está sendo executada em um container Docker
<h2> Acesse o tutorial em https://caiodelgado.dev/docker-101 </h2>
EOF
Agora que criamos nosso arquivo index.html
podemos executar nosso container novamente.
$ cd /vagrant/dockerfiles/lighttpd/
$ docker container run -d --name website -p 80:80 -v $PWD/index.html:/var/www/localhost/htdocs/index.html lighttpd
O parâmetro-v
ou--volume
é utilizado para os recursos de volumes, podendo ser diversos tipos de volumes, no nosso caso estamos enviando um arquivo$PWD/index.html
(a váriavel$PWD
significaPrint Working Directory
ou seja, o diretório atual ) para o caminho/var/www/localhost/htdocs/index.html
no container.
Como vocês devem ter notado, os mapeamentos de máquina hospedeira para o container seguem no formado<host>
+:
+<container>
Agora que executamos nosso container podemos verificar pelo navegador o endereço http://docker01.caiodelgado.example
ou através do terminal pelo curl
como informado no tópico de Network

Este post é o primeiro de uma série de posts onde utilizaremos o Docker.
Em um próximo post irei explicar como melhorar a qualidade das imagens bem como outros recursos mais avançados do Docker. Continuem acompanhando!
O código deste post encontra-se no repositório: https://github.com/caiodelgadonew/blog-docker-101
Ficamos por aqui e nos vemos em uma próxima.