Salve das Beleza! Continuando e finalizando, na verdade, a nossa saga aqui no nosso módulo de Docker, para que a gente consiga ter uma imagem otimizada para a produção mais otimizada ainda, baseado no que a gente viu na última aula, é necessário trabalhar com um recurso chamado de Multistage Build. chamado de Multistage Build. Esse recurso vai fazer com que a gente tenha ali vários estágios de build da imagem. Então, nós vamos poder usar, inclusive, várias imagens diferentes, se for necessário, para poder preparar essa imagem final. Então, dessa forma, a gente consegue deixar todo esse lixo que foi usado para a preparação dessa imagem descartada nessas imagens preparatórias e na final nós conseguimos pegar somente o resultado um executável ou somente o alvo do projeto jogando ali, deixando a imagem pequenininha e tendo aquelas vantagens todas, de custo de armazenamento, de tempo do build também, que acaba sendo acelerado, de transporte dessa imagem, de criação de containers, enfim. Então, bora aqui para a nossa aplicação. Nós vamos ver como que isso é aplicável aqui no Node.js, mas eu quero pegar um exemplo Golang, que inclusive esse exemplo está lá na documentação da Docker, e ele é bem categórico, muito simples, mas bem categórico. Vamos pensar o seguinte aqui, até tirar esse comentário daqui. Para poder trabalhar com a geração do executável ali de um golang, que é o que a gente vai acabar fazendo, vou precisar de uma imagem que tenha o golang. Então, temos aqui a imagem 1.21, certo? Mas vamos pensar o seguinte, na hora que eu gerar esse executável, na hora que eu gerar esse executável, eu não preciso mais do Golang para poder rodar. Vou só passar ali .bar o executável e ele já vai executar, porque a compilação já colocou tudo que é necessário ali dentro daquele executável. Então, eu continuando a manter uma imagem Golang, mesmo que ela seja Alpine, que ela seja Slim, estou desperdiçando uma série de armazenamento ali. Então, o Multistage Build vai servir para isso. Inclusive, eu vou até fazer algo diferente, que eu vou colocar isso aqui como Builder. E aqui nós podemos, em vez desse zero, passar builder. Eu prefiro trabalhar com nomes. Então, pensando que nós vamos ter ali o nosso código-fonte com os scripts.go, delimito um diretório para poder trabalhar, e o pessoal gosta de trabalhar com source, aí aqui nem tem um arquivo, um script goal, né? Está sendo gerado um main.goal diretamente aqui via terminal. Beleza. Então, vai ser feito ali o goal build e, inclusive, aqui, eu não quero nem entrar nessa seara com goal, tem como passar uma série de opções para poder tornar o executável menor ainda. Aí já seria uma questão específica da própria linguagem de programação. Então, essa imagem aqui é o primeiro estágio que vai ser para poder fazer a compilação. Então, eu vou ter o executável Hello. Aí eu posso, no segundo estágio, que vai ser a imagem final, usar a imagem scratch, que ela está disponível lá no Docker Hub. O scratch é o mínimo do mínimo do mínimo. Não tem nada dentro dessa imagem aqui. Nada, nada, nada. Então, eu utilizo ela e falo assim, olha, eu quero pegar lá do builder o bin hello e copiar isso para bin hello dentro do scratch. Aí, quando o container executar, vai executar esse hello, que na verdade vai só imprimir um hello world na tela. Então, a gente vai ver aqui o tamanho dessa imagem. Vamos lá. Vou entrar aqui no capítulo 9. Eu tenho... Ah, estou ainda dentro da... Acho que eu mantive dentro dessa pasta. Deixa eu só confirmar aqui se eu mantive... Ah, eu estou mantendo aqui dentro, então, da aula 01. Então, vou entrar na aula 01. O que que eu não tenho? Então vamos entrar aqui na pasta golang, a gente vai fazer aqui um build-t test golang, ponto. Está ali o dockerfile, ele vai baixar a imagem golang que tem aí os seus 100 megas eu acho a imagem até poderia até usar uma outra imagem, tem outra imagenzinha menor e aí acaba sendo mais rápido também, ele está baixando aqui uma imagem maior e aqui ele está terminando. Está extraindo ali os layers da imagem, está vendo? Está fazendo um extract aqui. Agora ele vai compilar, que vai ser bem rápido. Beleza. Então, vamos executar aqui o comando do ls com o teste golang. E olha só o tamanho dessa imagem 1.8 megas isso aqui nem está em compressão porque normalmente quando é armazenado no registry aplica a compressão então deve ficar menor ainda, agora vamos fazer aqui um outro teste então vou colocar aqui em vez de trabalhar com o multistageBuild, a gente vai colocar o Command como sendo o próprio Bean Hello, porque ele não gerou ali. Então, vou manter a imagem original como sendo a do próprio, a própria execução. Olha a diferença. 842. Eu nem imaginava que essa imagem Golang ela tem muita coisa, né? Mas, enfim, mesmo que fosse um Alpine aqui, uma Slim, não sei se tem Slim pra Golang, iria ficar aqui uns cento e tantos megas. A gente teve a economia pegando pra essa de cento e tantos, de 100 vezes. Então tudo que estava aqui nessa imagem ele é descartado. A gente tem sim o cache das camadas, a gente continua tendo os benefícios, mas a questão é que a imagem final inclusive aqui eu tenho que colocar builder, né? Que eu mudei ali em cima. A ideia é essa. Eu posso ter quantos estágios eu quiser. Eu preciso de 10 estágios pra poder gerar ali o meu projeto final. Tudo bem. Não tem problema nenhum. Então, vamos aplicar esse conceito aqui no Node. A gente vai ver uma diferença substancial. Vamos pegar aqui esse dockerfile de prod e eu vou colocar um prod com multistage, vou colocar assim multistage prod. Então vai ser a mesma imagem que a gente vai trabalhar. Então essa aqui eu vou chamar ela de builder. Essas informações devem estar na imagem final. informações devem estar na imagem final. Então a diferença do Node para o Golang é que o Golang é linguagem compilada, então a gente no final vai acabar precisando de uma imagem em 20 slim de novo. Mais daqui para cima, vamos ver o que a gente precisaria aqui. Na verdade, eu vou copiar tudo isso. Poderia ter deixado, né? Então, esse aqui é o builder. Mesmo que eu estou no builder, eu considerar a usar o usuário não root vou criar uma pastinha ali app para a gente poder trabalhar dentro da home node e a workdir também eu vou trabalhar no mesmo lugar aí aqui eu vou fazer um comando para poder copiar o package json. Agora na linha seguinte eu vou fazer aqui um npm ci. Isso aqui é uma dica específica para quem está no Node, e o ci vai otimizar a execução, ele faz determinadas verificações, tipo, se ele não tiver o lock que ele não executa, e outras validações também, então ele executa mais rápido que o install. Então, eu quero fazer aqui a cópia depois o build está igualzinho ao que a gente tem aqui embaixo. Mas o expose eu não preciso. O expose eu não preciso. Aí nós vamos fazer o build, instalando tudo aqui. Porque assim, a gente está instalando todo o ambiente, ele vai acabar instalando dev, dependence e dev dependence, e a gente acaba dependendo aqui, pelo menos, desse CLI do nest para poder fazer a compilação. Então, a gente está instalando tudo, mas olha só. A gente teve essa instalação, fez o build. Agora, eu vou criar mais um aqui, que eu vou chamar de production. Ou pode ser até até pode ser esse aqui eu não quero deixar como lixo essa parte vou chamar esse de production então eu vou ter o meu label aqui em cima, a gente até não colocou essas instalações mas eu vou ter que colocar aqui em cima porque na hora de não colocou essas instalações, mas eu vou ter que colocar aqui em cima, porque na hora de fazer o build ali, ele pode precisar. Às vezes eu posso ter que repetir aqui e aqui, porque ele precisa, mas às vezes aqui eu posso precisar de outras que aqui eu não preciso, então ele vai ser descartado. Aí eu vou ter o usuário node vou criar aqui o diretório mas aqui em cima vai ter uma diferença que lá em cima não teve, eu vou determinar que a variável node env que é a variável do node vai ser production porque aí na hora que eu fizer aqui o npm ci, que vai ser o comando que eu vou fazer a instalação, ainda eu vou pedir para ele poder fazer a instalação aqui somente dos pacotes de produção. Aí eu peço para ele poder limpar qualquer tipo de cache. Esse copy e esse build eu não tenho mais. Aqui, depois de fazer esse copy aí, eu vou fazer um copy do resultado lá do builder. Então, eu quero copiar from builder usando o shall node node. Então, eu quero copiar PromBuilder usando o shall node node. Eu quero copiar o que ele pegou ali, o que ele resultou na dist, copiando para a dist da minha máquina. Ou melhor, da minha máquina, não, da imagem de produção. Aí aqui eu posso rodar o prod no final. Então, de um certo modo, nós estamos instalando aqui mais dependências, principalmente com a NodeMods, e aqui nós estamos instalando apenas as de produção. Então, eu tenho aquela estratégia de cache olhando para esse builder que vai instalar dependências de desenvolvimento também, mas tenho a estratégia de cache quando eu olho para a produção, porque quando ele for fazer a instalação aqui, ele vai olhar somente para a produção. Então, vamos ver se essa imagem vai compilar. Vou colocar aqui um app Node.js. Aí o Docker build pega o da última aula. Aí que eu coloquei .multi stage.prod .multi Vamos rodar aqui esse cara. Ele está executando lá o builder. Agora já foi para lá para baixo. Também ele pega os caches que ele já tinha. Beleza. Agora, vamos rodar aqui o que a gente tem de tamanho nessa imagem. E a gente conseguiu reduzir, vocês lembram da última aula? Foi 383, a gente conseguiu reduzir para 265. 265 apenas usando essa estratégia. Como tem mais código aqui e aparentemente tem mais camadas, parece que vai ser maior, mas não é. Uma outra situação também legal de utilizar esse Multistage Build é que às vezes eu posso ter aqui um estágio intermediário, vamos supor que eu estou utilizando, sei lá, alguma outra imagem XPTO, que ela acaba não dependendo aqui do builder, por exemplo. Porque se o production depende do builder, isso aqui vai rodar de forma síncrona, ou seja, o builder tem que executar primeiro para depois executar o production. Mas se eu tiver um intermediário aqui, aí eu uso essa imagem XPTO aqui na produção para poder fazer a cópia de alguma coisa, então as duas são executadas em paralelo. Então nós temos essa otimização também. Aí aqui eu consigo ainda fazer uma redução maior que eu acrescentei naquele arquivo do RISM da aula passada, que são as imagens distros. Aqui elas não vão ter gerenciadores de pacote lá no seu userland, só vai ter mesmo o sistema operacional com a linguagem de programação. Essas imagens aqui, muitas delas são disponibilizadas pela própria Google. Pode dar uma pesquisada sobre isso, tem algumas versões. Então eu vou pegar aqui uma versão de um Node 20 com um Debian 12. Olha o que eu vou fazer. Eu vou substituir esse production que eu tenho aqui. Até aqui embaixo. Vou substituir por esse. Essa imagem está lá no GitHub Packages. Se ela não está no DocHub, eu preciso passar o caminho, né? Então, estou pegando aqui esse Node.js 20 Debian 12. Non-root significa que, então, ela já vai iniciar com um usuário chamado de non-root, que ele é um non-root. E eu estou colocando aqui como um distroless, mas isso aqui não tem necessidade. Aqui eu poderia já copiar. Na verdade, eu vou precisar da... eu vou precisar da de produção, sim. Então, nós vamos ter três estágios. Mas aqui eu não preciso desse distroless. A de produção, nós vamos gerar ali tudo bonitinho Beleza Ainda eu vou copiar a NodeModius E a dist daqui, tá? A ideia é essa Eu estou descartando mais coisas ainda Então nessa imagem eu só vou ter a dist A NodeModius e o Node Nem MPM eu vou ter Por dist, a nodeModules e o node, nem npm eu vou ter, por isso que eu estou colocando aqui para ele poder executar o dist main.js, que ele já força ali no entry point o node. Então bora lá, vamos ver se a gente consegue executar isso aqui. A gente vai ver a diferença de tamanho ainda. Pronto, executou. Eu já tinha isso aqui em cache também. Vamos fazer o comando. Reduziu para 182 MB. E ainda se a gente mexer mais algumas coisas aqui, tem como reduzir ainda mais. O problema de você trabalhar com a Distroless é que não tem nada. Então, tem npm. Você vai ter que executar qualquer outro comando, qualquer outra coisa diretamente ali, não vai ter mais nada. Então, a DistroLess serve para você poder chegar ali e copiar tudo que você precisa. Se você tem alguma ferramenta adicional que precisa ser colocada aqui, você vai ter que gerar o binário dela anteriormente, copiar ele para cá ou gerar o binário aqui fazendo um wget baixando o código. Então já saiba que exige um pouco mais de conhecimento de Linux, mas o resultado acaba sendo satisfatório. E aqui no caso eu posso colocar em barra app que esse usuário não root tem permissão Vamos rodar aqui essa imagem Vou fazer aqui Menos menos rm Aí o menos P Equivale a 3000 Test node Acho que é só isso Está rodando E Vamos fazer aqui Um curr test node. Acho que é só isso. Está rodando. Vamos fazer aqui um curr. Porta 3000 que vai mostrar um hello world. Pronto. Então, olha só. Aqui ele não vai conseguir parar. Vou fazer um docker stop. Beleza. Com a boa vontade dele, deixa ele parar a aplicação. E o último exemplo que eu tenho aqui para mostrar para vocês é de uma aplicação Python. Inclusive, de algumas coisas que a gente usa aqui internamente, olha o nível que a gente consegue fazer otimizações. E no caso desse Python aqui, a gente nem está utilizando o distro, mas dá para reduzir mais ainda. Na verdade, o Python que eu coloquei aqui, será que tem muita coisa? Ah, tem um AVM aqui. Vou colocar o Python para cá. Então, essa aqui é uma aplicação Python, uma aplicação, na verdade, Django. Vamos imaginar que no Django eu aqui é uma aplicação Python, uma aplicação, na verdade, Django. Vamos imaginar que no Django eu criei uma aplicação com o painel administrativo, vai ter a parte que os usuários vão acessar e tem a necessidade de colocar um JavaScript que às vezes eu estou trabalhando com ele através de módulos. E aí nós podemos ter um Laravel Mix, que é uma ferramenta do Laravel, não confundi com PHP, o Laravel Mix é apenas uma ferramenta JavaScript, ela trabalha ali com Webpack, para que você consiga gerenciar os seus CSS, JavaScript, tudo em módulo, e compila e gera um pacote no final. Então, imagina que eu preciso gerar um Django com esse JavaScript lá numa imagem e eu quero fazer tudo isso de forma otimizada. Então, olha aqui o Dockerfile que nós vamos fazer. Eu estou pegando uma imagem Python e Slim também, que é derivada de um Debian 3.9.0. Ela vai ser o builder. Então, eu habilito isso aqui para poder trabalhar sem os buffers do Python para poder agilizar o processo. Instalo dependências tudo em uma linha só e para poder gerenciar os pacotes, eu estou utilizando o PDM, que tem IP, PDM, Poetry, PPM, enfim. Mas essa imagem não tem um usuário restritivo. Eu criei com o user add, que é o comando lá do Debian para poder criar um novo usuário. Vai criar um usuário aqui com o ID 1000. Delimito a pasta de trabalho ali, barra home, barra Python, barra app, já que eu vou ter um barra home, barra Python. Aí eu copio o manifesto das dependências, igual o package JSON, tem aqui o pyproject. Então, dessa forma a gente consegue cache na hora de fazer a instalação. Mas no caso aqui do python, aí que você tem que conhecer a sua linguagem de programação. Tem uma situação que eu posso, ao invés de pedir para ele fazer a instalação direto, eu quero gerar os pacotes wheels. Então, eu peço para esse PDM gerar um requirements.txt que vai descrever todas as dependências ali no arquivo txt. E aí eu vou pedir para o pip instalar a parte desse arquivo, porque o pip consegue gerar esse wheel para a gente. Então, ele vai gerar os pacotes das dependências nessa pasta. Aí o will para a gente. Então, ele vai gerar os pacotes das dependências nessa pasta. Essa aqui é a primeira etapa. Eu tenho uma etapa somente para o Node, para poder gerar os meus ativos com o pilar. Então, isso aqui vai acontecer de forma paralela. Estou copiando o package JSON rodando um CI com NPM. Copio os arquivos necessários, rodo o comando lá e depois excluo a NodeMods aqui. Na verdade, isso aqui nem precisava, mas apenas de praxe. Beleza. Então, isso aqui vai acontecer em paralelo. Vou ter os pacotes do Python sendo gerados e tenho o meu JavaScript sendo gerado. Aí, aqui no final, que vai ser de produção, nós vamos trabalhar também sempre com o usuário no root, mas aí eu vou copiar o que eu tenho lá do meu builder, dentro do meu home.python.app, copio todo o conteúdo do projeto, eu acrescentei um dockerignore aqui também, aí eu tenho o meu source copiado, os meus pacotes wheels, e a minha pasta estática dos meus ativos ali bonitinho. Aí daqui para baixo, eu estou gerando esse run enorme para poder colocar o Nginx juntamente ali com o Python, porque o Nginx que vai conseguir servir esses arquivos de JavaScript, de CSS, lá para a minha aplicação web. Então, estou utilizando a versão Lite do Nginx que ela existe. E daqui para baixo, nós estamos fazendo, basicamente, habilitando o que é necessário para poder rodar com o usuário não root. Porque está vendo esses mkdir aqui? Como eu vou rodar um Nginx com Python, se eu não fizer esses mkdir aqui de proxy, de BOR e de fastcgi, ele não vai conseguir a execução. Mas, no final das contas, eu estou fazendo um pip, pegando lá os pacotes wheels e aí fazendo a instalação aqui, que ele vai gerar a instalação muito mais rapidamente. Aí, no final, eu tenho que dar permissão aqui no nginx.pd, que é o lugar onde vai ter o PID do Nginx que vai rodar. Estou tirando a primeira linha do nginx-conf para permitir que a gente rode ele como não root. Esse sed aqui vai tirar a primeira linha do arquivo. E esse arquivo também está sendo copiado. Ele está aqui dentro de Docker. Então, o arquivo vai poder rodar junto com... ele vai rodar redirecionando lá para o Python, que vai rodar na porta 8000. E se eu colocar um barra estética, eu vou conseguir acessar os meus ativos. Aí no Docker, ele ignora. Eu tenho que ignorar Git, VNV, coisas assim, para poder evitar qualquer cópia desnecessária. Está aqui o Nginx sendo copiado, o usuário Python sendo determinado aqui. É aqui que eu determino, porque daqui para cima eu preciso do root ainda para poder trabalhar. E aqui o Startprod, que eu preciso rodar um camaradinha, que é um servidorzinho com Python que é o Gunai Core para poder levantar o Django e roda juntamente o Nginx de modo paralelo. Então, vamos rodar esse carinha aqui também. Na verdade, deixa eu pegar aqui o outro projeto que é mais fácil, né? Só entrar aqui em Python. Vou fazer aqui um build-st Python Dockerfile. Aqui eu já tinha ele pronto, mas agora eu posso rodar Docker run. Aí aqui o Nginx está rodando. Um detalhe importante é isso. O Nginx está rodando na 8080 e não na 80. Então, eu vou rodar aqui 8080 na porta 8000. Deixa eu pegar um browser qualquer aqui. Vamos lá. Então, 8080. Melhor, 8080 não. 8000. E será que ele não subiu? Ah, pera, eu rodei o Node, né? Vamos aguardar. Beleza. Aí aqui vai ser teste Python. Ele não vai mostrar nenhuma saída, isso aqui que está aparecendo para vocês é um atalho que eu aprecio para poder gravar, só para não confundir. Então está aqui o Django, eu abri ele e a pasta estética aqui. E... Aqui ele está, é 8000. Está aqui. Então eu consegui abrir também a pasta estética, tem aqui a pasta dist, lá o console.log que eu coloquei, estou acessando tudo de dentro do container, então quando a gente trabalha com esse princípio do mínimo privilégio é o que aconteceu aqui, as portas estão fechadas para o usuário Python. Então, a gente teve que... Isso aqui é o script SH que vai rodar aqui embaixo. Tem que fazer um chmod nele. Mas eu estou fazendo um auto-remove-y, um clean para poder remover coisas desnecessárias. Aquele rm-rf nas listas e também nos pacotes wheels que não são mais necessários. Aí, daqui para baixo, é só mkdir e xal para poder habilitar o Nginx ali, que quando for rodar com esse usuário Python, ele vai dar erro para poder trabalhar com essas pastas. Então, a gente tem que criar tudo isso. Então, aqui também, a pasta lib do Nginx, eu estou jogando ela para o meu usuário. O Nginx conf também para o meu usuário. E fazendo um touch ali do pid do Nginx, porque senão não vai ter permissão para poder criar. Aí, no final das contas, um shout também para o meu usuário, e removendo ali a primeira linha para a gente poder rodar no 8080, porque o usuário comum não tem permissão padrão para poder rodar no 80, aí tem que fazer essas modificações para ele fazer dessa forma. Ainda se a gente quisesse, aí entraria mais uma otimização. ainda se a gente quisesse, aí entraria mais uma otimização. Eu poderia gerar mais uma etapa para só... Se bem que aqui, se a gente fosse gerar mais uma etapa, talvez valeria a pena, porque eu posso ter algum lixo ainda e só copio o que é necessário aqui dos pacotes que foram gerados, dos site packages, para quem... Aí é do Python a pastinha que ele vai instalar as libs com pip. E a gente poderia usar uma distroless também, mas aqui nessa casa sem magia tem algumas coisas que são necessárias, mas acho que elas já foram automaticamente instaladas aqui. Por exemplo, se eu tivesse alguma lib que tivesse que rodar nessa distroless, eu teria que já ter gerado ela antes e copiar somente o binário lá, enfim então nós temos esses desafios, a imagem de produção ela sempre é maior, mas a ideia é ter imagens que vão ser pequenas, vamos dar uma olhada nessa imagem Python aqui, Ela tem apenas 161, eu nem estou usando distros, se eu quisesse usar uma distros aqui ainda, iria otimizar mais ainda. Então a pegada é sempre essa, veja todas as dicas da aula e também do que eu coloquei aqui nesse reading, porque está tudo condensado aqui pessoal Então, fechamos o nosso módulo. Eu só vou para a aula de conclusão para a gente poder colocar a pá de cal em cima. É isso aí e até a próxima.