DockerFileNr 2 W tym wpisie stworzymy masę obrazów Docker do różnych środowisk i do różnych języków programowania. Na razie zapomnimy o Kubernetes. Zajmiemy się nim w następnym wpisie.

W poprzednim wpisie utworzyliśmy prosty obraz docker, który zawierał tylko statyczne pliki. Uruchomiliśmy go i tyle.

Oczywiście zabawa z Docker nie kończy się tutaj. Wróćmy do polecenia tworzenia obrazu  :

docker build -t mojblog:mojtag2 .

Jak widzisz przy tworzeniu obrazu możesz podać tag. Jeżeli go nie podasz to twój obraz dostanie tag ":latest".  O ile dla zabawy, jeśli tworzysz obraz to nie ma to dużego znaczenia to już prawdziwym życiu to inna sprawa.

Pomyśl, że przecież obrazy aplikacji można tworzyć per :

  • Wydanie
  • Poprawka
  • Wykonane zadanie
  • Dzień

Oznacza to, że możesz posiadać wiele obrazów tej samej aplikacji, które mogą się różnic małymi zmianami dodanymi przez innych programistów. 

Jak jednak oznaczyć te obrazy? O tego właśnie są tagi i jak się domyślasz domyślny tag "latest" prawdopobnie za bardzo by ci nie pomógł, ponieważ nie masz żadnej informacji do czego ten nowy obraz się odnosi.

Dlatego do tagów często dodaje się różne rzeczy wszystko zależy od konwencji : może to być data, może to być numer poprawionego błędu, może to być numer wdrożenia.

Zbudujmy aplikacje w języku programowania GO

Spokojnie nie musisz znać tego języka programowania. 

Od kiedy ty umiesz Go

Stworzymy przy użyciu jego prosty serwer HTTP. Moja aplikacja składa się z dwóch plików. Skoro chcemy utworzyć obraz to także potrzebujemy mieć plik "dockerfile"

Projekt programu GO

W pliku "go.mod" mówimy o wersji języka oraz o tym, jak nasz moduł będzie się nazywał.

module helloapp

go 1.15

W pliku "main.go"  stworzymy właśnie prostą logikę serwera HTTP. Jest ona na tyle prosta, że nawet nie znając składni widać co ten język programowania robi.

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintln(w, "Cześć");
}

func main() {
    http.HandleFunc("/",handler)
    fmt.Println("server started")
    log.Fatal(http.ListenAndServe(":8090", nil))
}

Na początku importujemy potrzebne nam funkcjonalności. W funkcji main() będziemy łapać zapytania HTTP do domyślnej ścieżki. W tej funkcji ustawiamy także port naszego serwera :"8090". 

Funkcja Handler natomiast określa co ma być wyświetlone, gdy użytkownik wejdzie na naszą stronę.

Teraz przejdźmy do Dockerfile

FROM golang:latest

#ustawia pracujący folder
WORKDIR /helloApp

#kopiuje to pliki na kontener
ADD . .

#budujemy aplikacje
RUN go build -o main .

EXPOSE 8090

CMD ["/helloApp/main"]

Nasza aplikacja GO będzie korzystała z portu 8090 więc musimy to określić w naszym dockerfile poprzez 8090. Na razie kopiujemy wszystkie pliki do naszej aplikacji poprzez alternatywną operacje kopiowania "ADD". Za chwilę się dowiemy czy to dobry pomysł

Możesz się zastanawiać jakie polecenie jest lepsze. "ADD" czy "COPY". Według dokumentacji oba polecenia są tak samoużyteczne, ale zaleca się do korzystania bardziej z operacji "COPY".

Korzystamy ze środowiska GO więc potrzebujemy do niego obrazu bazowego. Na razie jest to golang:latest. 

Pozostało nam w wierszu poleceń wejść do folderu gdzie jest nasza aplikacja i napisać :

docker build -t mojObrazGo:wdrozenie518

Otrzymasz jednak błąd, ponieważ wielkość liter w deklarowaniu nazwy obrazu ma znaczenie

Wielkość liter ma znaczenie

Napiszmy więc poprawnie to polecenie :

docker build -t mojobrazgo:wdrozenie518

Sprawdź czy rzeczywiście obraz się utworzył 

docker images

docker images

Pozostało na uruchomić obraz :

docker run --rm -it -p 90:8090 mojobrazgo:wdrozenie518

Argument "-it" uruchamiana aplikacje w środowisku interaktywnym w tym przypadku jest to wiersz poleceń

Argument "--rm" usunie automatycznie kontener, gdy zatrzymam działanie wiersza poleceń.

Argument "-p" robi przekierowanie portów. Aplikacja uruchamia się w porcie :8090 i przekierowujemy go na "90"

Nie podaliśmy tym razem nazwy kontenera więc Docker wygenerował ją za nas.

Sprawdź czy pod adresem localhost:90 rzeczywiście nasza aplikacja działa

localhost

W środowisku graficznym Docker też możesz sprawdzić jaką nazwę ma twój konetner

nazwa kontenera

Co można zrobić lepiej ?

Po pierwsze warto określić, na jakiej konkretnej wersji obrazu chcemy pracować. Ja wiesz .NET CORE 2.1 to nie .NET CORE 3.2. Java JDK 7 to nie JDK 8. Python 2 to nie Python 3.

Pobieranie więc najnowszej wersji obrazu bazowego zawsze do naszej aplikacji w którymś momencie ugryzie na w tyłek.

Dlatego musimy zmienić golang:latest na jakąś konkretną wersje. Na przykład na golang:1.12-alpine.

Dodatkowo warto tutaj omówić fazy budowania obrazu. Otóż nie wszystko musimy wrzucać ostatecznie do naszego obrazu. Możemy najpierw zbudować aplikacje mają obraz bazowy, a potem z tego etapu budowania tylko skopiować pliki naszej zbudowanej aplikacji.

Co to da? Możesz dzięki temu obniżyć wielkość obrazu nawet 800 MB. W końcu twój obraz nie powinien zawierać całej logiki obrazu bazowego (golang:1.12-alpine)

FROM golang:1.13-alpine AS build

#ustawia pracujący folder
WORKDIR /src

COPY main*.go go.* ./

#budujemy aplikacje
RUN CGO_ENABLED=0 go build -o /bin/example2_2
#druga faza FROM scratch COPY --from=build /bin/example2_2 /bin/example2_2 EXPOSE 8090 CMD ["/bin/example2_2"]

Najpierw chcemy zbudować obraza tak jak wcześniej, a potem to co zbudowaliśmy przeniesiemy do pustego obrazu.

From scratch to specjalne odniesienie do najmniejszego obrazu, jaki Docker może stworzyć. W takim obrazie nic nie ma.

Do tego pustego obrazu skopiujemy już skompilowane pliki. Nazwijmy to drugą fazą naszej operacji.

Potem uruchomimy nasz program i powiemy, że potrzebujemy portu :8090.

docker:build -t mojobrazgolepszy:wdrozenie59 .

Każde polecenie w Dockerfile tworzy kolejny krok. Jak widzisz pewne kroki się powtarzają więc są one wyciągane z pamięci Cache. Pod tym względem też warto modyfikować swój Dockerfile.

Kroki budowania obrazu

Nasz nowy obraz waży tylko 7.4 MB. Natomiast stary obraz, który zawierał w sobie całe środowisko języka GO waży 845 MB.

Mój lepszy obraz GO

Duża różnica jak widzisz. Pozostaje jeszcze sprawdzić czy nasza aplikacja działa tak samo.

docker run --rm -it -p 90:8090 mojobrazgolepszy:wdrozenie519

Zbudujmy obraz aplikacji konsolowej w .NET Core 3.1

Jak budowanie obrazu Docker wygląda w .NET? Spokojnie na .NET 5 też spojrzymy w tym wpisie :)

Kolejny przykład jak bardzo są ważne wersje frameworków.

aplikacja asp.net core

Do projektu oprócz Dockerfile możesz także dodać ".dockerignore". Ten plik będzie określał, jakie plik mają zostać zignorowane w trakcie kopiowania.

W zależności od potrzeb może on wyglądać prymitywnie. 

# directories
**/bin/
**/obj/
**/out/

# files
Dockerfile*
**/*.md

...może on też wyglądać na bardzo rozbudowaną listę w końcu nigdy nie można być bezpieczny za bardzo

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

Działanie tego pliku jest podobne do "gitignore".

Wróćmy jednak do naszego przykładu aplikacji .NET CORE 3.1. Tym razem to nie będzie aplikacja serwerowa, która wyświetlić coś w przeglądarce. Tym razem utworzymy aplikacje konsolową. Tak takie aplikacje też można skonteneryzować.

class Program
{
    static async Task Main(string[] args)
    {
        var counter = 0;
        var max = args.Length != 0 ? Convert.ToInt32(args[0]) : -1;
        while (max == -1 || counter < max)
        {
            
            Console.WriteLine($"Counter: {++counter}");
            await Task.Delay(1000);
        }
    }
}

Aplikacja ta będzie wyświetlać co sekundę nową liczbę zwiększana co jeden. Możesz w argumencie podać maksymalną liczbę, do której aplikacja może dojść. 

Nasz dockerfile wygląda tak . Chociaż ostrzegam na razie w dużo jest w nim błędów

FROM mcr.microsoft.com/dotnet/aspnet:3.1
    
COPY bin/Release/netcoreapp3.1/publish/ App/
WORKDIR /App
ENTRYPOINT ["dotnet", "NetCore.DockerTest.dll"]

Ten dockerfile dużo zakłada. Zakłada on, że paczki NuGet tej aplikacji są już pobrane. Zakłada on, że aplikacja już została zbudowana i opublikowana przez Ciebie np. w Visual Studio.

Dodatkowo pobierze on wszystkie pliki z folderu "bin/Release/netcoreapp3.1/publish/" być może też te niepotrzebne.

Dlatego u Ciebie jak pobierzesz ten przykład z GitHuba coś może nie zadziałać.

Zbudujmy ten obraz

docker build -t mojobraznetcore3:tentag .

Przy uruchomieniu obrazu pamiętaj też o dodawaniu tagu. Domyślnie wtedy Docker będzie szukał tagu "latest"

docker i tagi

Nie ma sensu podawać tutaj portu, gdyż jest to aplikacja konsolowa.

docker run mojobraznetcore3:tentag
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5

Przy uruchomieniu zobaczysz aplikacje w akcji. Nawet gdy wyjdziesz z tego podłączenia się do aplikacji to kontenerem nadal będzie istniał. Dlaczego? Bo nie dodaliśmy parametru "--rm"

docker container ls

lista kontenerów

W każdej chwili możesz ponownie podłączy swój wiersz poleceń do tej konsoli. Musisz tylko znać nazwę swojego kontenera lub jego ID. Te parametry u Ciebie będą inne.

docker attach 25a931cb6039
Counter: 198
Counter: 199
Counter: 200

Spróbuj usunąć kontener po jego nazwie.

docker container rm epic_chebyshev

Dowiesz się, że nie możesz usuwać kontenerów, gdy są one uruchomione. Dlatego zatrzymajmy go. 

docker container stop epic_chebyshev

Teraz obraz może być usunięty.

Samą aplikacje konsolową w tym obrazie możesz uruchomić z parametrem. Możesz też używając parametru "--rm" określić jej automatyczne likwidacje kontenera, gdy skończy ona swoją pracę.

docker run -it --rm mojobraznetcore3:tentag 7
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Counter: 6
Counter: 7

PanNiebieski@CEZMSI 
>

Spójrzmy na inne obrazy docker dla aplikacji ASP.NET CORE z .NET 5.

Zbudujmy obraz aplikacji ASP.NET CORE w .NET 5

Pora bardziej zaawansowany przykład z lepszym dockerfile.

Zwłaszcza że na pomoc idzie GitHub z oficjalnymi poradami jak taki obrazy utworzyć : https://github.com/dotnet/dotnet-docker

Aplikacja asp.net core projekt

Mamy więc aplikacje ASP.NET CORE napisaną już w .NET 5.

a-13.PNG

Nie będziemy wnikać jak ta aplikacja jest zbudowana. Możesz to samo zrobić tworząc nowy szablon projektu ASP.NET CORE w wersji .NET 5.

Jak więc wygląda dobry dockerfile dla .NET. Po pierwsze najpierw musimy skopiować tylko plik projektu. Mając plik projektu możemy pobrać brakujące paczki Nuget poprzez polecenie "dotnet restore".

Potem kopiujemy wszystko inne i budujemy aplikacje.

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /source

# skopiuj csproj i niech pobierze on brakujące paczki NUGET
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore

# skopiuj wszystko inne i zbuduj aplikacje
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app --no-restore

# ostatnie faza tworzenia obrazu i jego ostania forma
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]

Tak przechodzimy do drugiej i ostatniej fazy. Zaczynając od nowa od obrazu bazowego .NET 5 kopiujemy do niego plik skompilowane  z poprzedniego obrazu (--from=build) 

Ma to sens po co nam w obrazie pliki kodu C# i inne śmiecie. Nas interesują tylko pliki, które wykonują faktycznie kod. 

Na koniec mówimy jaka biblioteka dll rozpoczyna aplikacje w tym wypadku jest to "aspnetapp.dll".

Domyślnie aplikacja ASP.NET CORE uruchamia się na porcie 80. Jeśli chcesz to zmienić musisz nadpisać zmienną środowiskową. Oto jak to zrobić :

ENV ASPNETCORE_URLS=http://+:5555

Zbudujmy obraz złożonej aplikacji konsolowej w .NET 5

A co jeśli twój projekt składa się z wielu bibliotek?

Wiele bibliotek w .NET 5 projekt

Mam tutaj dwa projekty biblioteki oraz jedną aplikacje konsolową.

Wiele bibliotek w .NET 5 projekt rozwinięcie

Jak się domyślasz w pliku docker musimy je odpowiednio przebudować i pobrać paczki NuGet, a potem skompilować.

Dodatkowo mamy tutaj przykład użycia projektu testowego.

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /source

# skopiuj csproj i niech pobierze on brakujące paczki NUGET
COPY complexapp/*.csproj complexapp/
COPY libfoo/*.csproj libfoo/
COPY libbar/*.csproj libbar/
RUN dotnet restore complexapp/complexapp.csproj

# skopiuj i zbuduj aplikacje i jej biblioteki
COPY complexapp/ complexapp/
COPY libfoo/ libfoo/
COPY libbar/ libbar/
WORKDIR /source/complexapp
RUN dotnet build -c release --no-restore

# faza testów -- exposes optional entrypoint
# target entrypoint with: docker build --target test
FROM build AS test
WORKDIR /source/tests
COPY tests/ .
ENTRYPOINT ["dotnet", "test", "--logger:trx"]

FROM build AS publish
RUN dotnet publish -c release --no-build -o /app

# ostatnia faza i ostatnia forma obrazu
FROM mcr.microsoft.com/dotnet/runtime:5.0
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "complexapp.dll"]

Ostatecznie wszystkie obrazy pośrednie zostaną odrzucone i tylko skompilowane zawartości zostaną umieszczone do ostatniego obrazu.

Zbudujmy obraz Java Spring

O ile nie jestem specjalistą od Javy, a tym bardziej frameworku Spring to byłem mile zaskoczony, że udało mi się zrobić ten prosty projekt HelloWorld.

Spring to framework do budowania aplikacji internetowych i niektórzy twierdzą, że jest on lepszy niż ASP.NET. Kto wie ja nie oceniam, ja zarabiam pieniądze na technologiach, w których piszę oprogramowanie.

Nawet nie wiem czy ma zainstalowane co trzeba aby tą aplikacje uruchomić, ale z Dockerem to bez znaczenia. 

Od kiedy ty umiesz Jave

W tym projekcie mamy definicję aplikacji oraz jeden kontroler.

Zbudujmy obraz Java Spring

W pliku DemoApplication.java uruchamiamy Spring.

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

Nasz kontroler HelloController dla domyślnego adresu wyświetli napis "Pozdrowienia z Spring Boot".

package com.example.demo;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Pozdrowienia z Spring Boot!";
    }

}

W pliku pom.xml definiujesz co potrzebuje ta aplikacja potrzebuje do działania oraz jaki plik JAR z niej wyskoczy po kompilacji. Będzie on nazywał się "demo"

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Proces budowania obrazu tej aplikacji korzysta z dwóch obrazów bazowych. Obraz bazowy Maven zbuduje tą aplikacje o pobierze odpowiednie paczki i biblioteki. 

Obraz bazowy OpenJDK:8 będzie uruchamiał tą zbudowaną już aplikacje. Do tego obrazu załączymy zbudowane pliki naszego projektu.

#pierwszy etap budowania aplikacji użyje obrazu rodzica maven 3.6.1
FROM maven:3.6.1-jdk-8-alpine AS MAVEN_BUILD 

# kopiowanie pom i src do kontenera
COPY ./ ./  

# pakowanie kodu naszej aplikacji
RUN mvn clean package 

#drugi etap budowania aplikacji używając jdk 8 na alpine 3.9
FROM openjdk:8-jre-alpine3.9 

# skopiuj tylko to co potrzebujemy z pierwszego etapu, a wszystko inne odrzuć
COPY --from=MAVEN_BUILD /target/demo-0.0.1-SNAPSHOT.jar /demo.jar 

# ustaw polecenie startowe aby uruchomić JAR
CMD ["java", "-jar", "/demo.jar"]

Pozostało zbudować sam obraz.

docker build -t mojspring:wydanie1205 .

No i oczywiście go uruchomić :

hello world z spring

Zbudujmy obraz Python

Przykład z Python jest najprostszy, ze wszystkich omawianych do tej pory przykładów.

projekt Python

Mój skrypt Python skorzysta biblioteki DataMatrixEncoder. Potrafi ona zmienić obrazek, który jest kodem kreskowy na tekst ASCII, który ten kod będzie reprezentować. 

Plik datamatrix_test.png jest zaszyty w tej bibliotece.

from pystrich.datamatrix import DataMatrixEncoder

encoder = DataMatrixEncoder('This is a DataMatrix.')
encoder.save('./datamatrix_test.png')
print(encoder.get_ascii())

Oto obraz docker to Pythona.

FROM python:3

ADD my_script.py /

RUN pip install pystrich

CMD [ "python", "./my_script.py" ]

Co można tutaj zmienić? No na przykład znaleźć chudsza wersje bazowego obrazu Python.

FROM python:3.7.5-slim

ADD my_script.py /

RUN pip install pystrich

CMD [ "python", "./my_script.py" ]

Jak widzisz biblioteka do uruchomienia skryptu nie znajduje się w moim folderze. Zostanie ona pobrana w trakcie budowanie obrazu Docker.

Pozostaje nam zbudować obraz i..

docker build -t python-barcode .

...go uruchomić.

docker run --rm python-barcode

Oto efekt : 

python-barcode docker

Proste prawda 

Przykłady dobrego dockerfile na podstawie NodeJS

Jak widzisz tworzenie obrazów Docker jest bardzo łatwe i w sumie jak widzisz pewne mechanizmy się powtarzają niezależnie czy to jest C#,Java, Python czy GO.

Na koniec pokaże Ci przykład pliku Docker File dla NodeJS. Moja aplikacja NodeJS zawiera tylko jeden plik, który odpali serwer na porcie 8080 i dla domyślnej ścieżki napisze "Hejka z Node JS".

const express = require("express");
const app = express();

app.listen(8080, function () {
  console.log("listening on 8080");
});

app.get("/", (req, res) => {
  res.send("Hejka z Node JS");
});

Oto plik package.json

{
  "name": "docker_web_app",
  "version": "1.0.0",
  "description": "Node.js on Docker",
  "author": "First Last <first.last@example.com>",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}

...

Co z obrazem? NodeJS jest dobry przykładem tego, co można zrobić w nim źle.

Wyobraź sobie, że na początek mamy taki dockerfile. 

Przykłady dobrego dockerfile na podstawie NodeJS 1

Oczywiście będzie on działać, ale jest tutaj dużo rzeczy do poprawny

Po 1 : Kolejność instrukcji, czyli co warto wsadzić w cache

Na początku nie warto kopiować wszystkiego. Podobnie działanie widziałeś dla aplikacji .NET gdzie tylko "csproject" był pobierany.

Mając tylko plik package.json możemy potem odpalić NPM i pobrać potrzebne paczki do folderu NodeModules. Package.json nie będzie się często zmieniał więc ta operacja kopiowania pójdzie do pamięci cache. Przyspieszy to ponowne budowanie obrazu.

Po 1 : Kolejność instrukcji, czyli co warto wsadzić w cache

Po 2 : Bardziej specyficzne kopiowanie

W sumie, jeśli chodzi o drugi etap kopiowania to nie musimy kopiować wszystkiego. W tym przypadku wystarczy na jeden plik "index.js".

Po 2 : Bardziej specyficzne kopiowanie NodeJS Docker

Po 3 : Łączenie poleceń

Każde polecenie RUN to nowy krok w Docker. Dlaczego więc nie połączyć wszystkie polecenia w jeden ciąg RUN?

Po 3 : Łączenie poleceń Node JS Docker

Możemy określić dokładnie co powinno się zainstalować w NodeJS, ale pozostawmy informacje instalacyjne w pliku package.json.

Po 4 : Usuń zbędne rzeczy

Do polecenia możesz dodać opcje pominięcia opcjonalnych paczek, które są często załączane do bibliotek JavaScript.

Po 4 : Usuń zbędne rzeczy Docker NodeJs

Dodatkowo na koniec warto w NPM wyczyścić Cache tak aby obraz Docker był, jak najmniejszy.

Po 4 : Usuń zbędne rzeczy

Na koniec pozostało ustalić konkretną wersje obrazu i jak to zrobiliśmy dla obrazu Python znaleźć jego najmniejszą wersje.

Po 5 : Ustal konkretną wersje obrazu i wybierz najmniejszą

Po 5 : Ustal konkretną wersje obrazu i wybierz najmniejszą NodeJS Docker

Oto ostateczna wersja docker file dla Node.js.

FROM node:12

WORKDIR /app

COPY package*.json ./

RUN npm config set registry http://registry.npmjs.org/ \
        && nom install --no-optional \
        && npm cache clear

COPY index.js ./

EXPOSE 8080
CMD ["node","index.js"]

To wszystko, jeśli chodzi o Dockera na razie.

W następnym wpisie zrobimy głęboki skok do Kubernetes.