Build
Introducció
Section titled “Introducció”Un Dockerfile és un fitxer de text que té instruccions de com construir una imatge.
Servidor python
Section titled “Servidor python”Crea un fitxer amb el nom de Dockerfile
amb aquest contingut:
FROM alpine:latest
La primera línia del fiter sempre ha de ser la imatge que utilitzem com a punt de partida, en el nostre cas una alpine
x[https://hub.docker.com/_/alpine]
Ja pots construir una imatge utilitzant aquest fitxer de text:
$ docker build --tag server ....Step 1/1 : FROM alpine:latestlatest: Pulling from library/alpined25f557d7f31: Pull completeDigest: sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb4f25238ebdStatus: Downloaded newer image for alpine:latest ---> 1d34ffeaf190Successfully built 1d34ffeaf190Successfully tagged server:latest
Pots veure que ara tenim dos imatges, una alpine
i una server
amb el mateix IMAGE ID
:
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine latest 1d34ffeaf190 2 weeks ago 7.79MBserver latest 1d34ffeaf190 2 weeks ago 7.79MB
La nostra imatge server
és la mateixa que l’alpine
i la pots utilitzar igual que faries servir una imatge alpine
.
$ docker run --rm server cat /etc/*release*NAME="Alpine Linux"ID=alpineVERSION_ID=3.20.0...
A continuació el que has de fer es crear una sessió interactiva amb un contenidor server
per explorar quines ordres has d’utilitzar per crear un servidor python, i un cop tingues clar quines ordres has d’utilitzar les escrius al Dockerfile
.
$ docker run --rm -it server sh... $ python sh: python: not found
Doncs per començar python no està instal.lat per defecte a diferència de la majoria dels sistemes operatius Linux.
Les imatges alpine
són les més lleugeres i es fan servir per crear imatges noves perquè noves porten lo just i necessari per funcionar.
Doncs, instal.lem Python:
... $ apk update && apk add python3 ... Executing busybox-1.36.1-r28.trigger OK: 50 MiB in 31 packages
$ python3 --version Python 3.12.3 $ exit$
A continuació ja pots modificar el fitxer Dockerfile
:
FROM alpine:latestRUN apk update && apk add python3
Amb RUN
indiquem un ordre que s’ha d’executar de la mateixa manera que abans ho hem fet manualment.
Torna a construir la imatge:
$ docker build --tag server ....Step 1/2 : FROM alpine:latest ---> 1d34ffeaf190Step 2/2 : RUN apk update && apk add python3 ---> Running in ca2a8bdcc7b3...(3/17) Installing libffi (3.4.6-r0)(4/17) Installing gdbm (1.23-r1)...
Aquest cop la construcció es fa en dos passos, lStep 1/2
i lStep 2/2
.
Mira que ha passat amb les imatges:
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEserver latest 713e9ed4cc61 5 seconds ago 50.7MBalpine latest 1d34ffeaf190 2 weeks ago 7.79MB
Pots veure que el IMAGE ID
és diferent i que la nostra imatge s’ha engreixat una mica.
I que la nostre imatge server
porta els fitxers pyhton i podem executar python3
:
$ docker run --rm server python3 --versionPython 3.12.3
La lògica a seguir per construïr un fitxer Dockerfile
és anar pas a pas (o step
a step
)
- Crear una nova imatge
- Arrencar una sessió interactiva a partir de la nova imatge i fer les proves pertinents
- Crear un nou pas o
Step
.
Per tant, el proper pas és arrencar un servidor python:
$ docker run --rm -it server sh ...... $ python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Obre un altre terminal i verifica que el servidor web està funcionant:
$ lynx localhost:8080
Pots veure que és un servidor bàsic que et permet navegar pel sistema de fitxers:
{% image “server.png” %}
ENTRYPOINT
Section titled “ENTRYPOINT”Surt del contenidor i verifica una cosa molt interessant.
Quan arrenques un servidor apache
aquest per defecte executa un servidor apache:
$ docker run --rm -d --name apache -p 80:80 httpd05178b33725122c603307676620a03c07b01ff14965dda12053e711d688f06f8
$ curl localhost<html><body><h1>It works!</h1></body></html>
$ docker stop apacheapache
Però quan arrenques un servidor server
no passa res:
$ docker run --rm -d --name server -p 80:8000 serverf1c3ffd4db1af483585d2f417ecef03406b486e87a4c265480850c72b3915074
$ curl localhostcurl: (7) Failed to connect to localhost port 80 after 0 ms: Connection refused
El contenidor s’ha parat i eliminat:
$ docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Has d’afegir un ENTRYPOINT
, una ordre que s’executa per defecte quan s’arrenca un contenidor:
FROM alpine:latestRUN apk update && apk add python3ENTRYPOINT python3 -m http.server
Crear una nova imatge i arrenca un contenidor:
$ docker build --tag server .$ docker run --rm -d --name server -p 80:8000 server$ lynx localhost
El servidor està funcionant!
El procés 1 és l’ordre python3 -m http.server
:
$ docker exec server psPID USER TIME COMMAND 1 root 0:00 python3 -m http.server 8 root 0:00 ps
Llavors la imatge httpd
ha de tenir un entrypoint!
Anem a veure quina:
$ docker inspect --type=image --format='{% raw %}{{json .Config.Entrypoint}}{% endraw %}' httpnull
Doncs no en té 🤔! I la teva?
$ docker inspect --type=image --format='{% raw %}{{json .Config.Entrypoint}}{% endraw %}' server["/bin/sh","-c","python3 -m http.server"]
Doncs si.
Anem a veure que passa amb httpd
🧐
$ docker run --rm -d --name apache httpd846689cd9daeb2264e6d0cfc1b31a519d191ccce8b048e5b3ea68af35d0deb66
$ docker exec -it apache bash... $ apt update && apt install -y procps $ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 5860 4624 ? Ss 15:35 0:00 httpd -DFOREGROUND www-data 9 0.0 0.1 1931448 9724 ? Sl 15:35 0:00 httpd -DFOREGROUND ...
Llavors com és que funciona sense un ENTRYPOINT
?
Si mires el DockerFile
de httpd:2.4
pots veure que al final no hi ha un ENTRYPOINT
sinó un CMD ["httpd-foreground"]
.
Però abans de continuar, t’has donat compte que les imatges es construeixen més ràpid que al principi?
Torna a construir la imatge sense modificar el fitxer Dockerfile
i pots veure que es fa en un moment:
$ docker build --tag server ....Step 1/3 : FROM alpine:latest ---> 1d34ffeaf190Step 2/3 : RUN apk update && apk add python3 ---> Using cache ---> 713e9ed4cc61Step 3/3 : ENTRYPOINT python3 -m http.server ---> Using cache ---> 34adee7ad443Successfully built 34adee7ad443Successfully tagged server:latest
Pots veure que en cada step
s’indica que s’està utilitzant la cache ---> Using cache
.
Aquestes caches són imatges, però on estan?
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEserver latest 34adee7ad443 57 minutes ago 50.7MBalpine latest 1d34ffeaf190 2 weeks ago 7.79MBhttpd latest 356125da0595 2 months ago 147MB
Enlloc de buscar a Google o ChatGPT li pots preguntar directament a docker
:
$ docker images --help...Options: -a, --all Show all images (default hides intermediate images) ...
Per defecte docker images
no mostra les imatges intermitges, però amb l’opció -a
t’ensenya totes:
$ docker images -aREPOSITORY TAG IMAGE ID CREATED SIZEserver latest 34adee7ad443 About an hour ago 50.7MB<none> <none> 713e9ed4cc61 2 hours ago 50.7MBalpine latest 1d34ffeaf190 2 weeks ago 7.79MBhttpd latest 356125da0595 2 months ago 147MB
Ara podem veure la imatge 713e9ed4cc61
de l’step 2/3
.
I ja de pas, no hi ha imatges alpine
amb Python?
Doncs si, a Docker Hub les pots trobar, a més amb la versió de Python que tu vulguis: Docker Hub - Python.
També hi imatges alpine
amb Apache: Docker Hub - httpd.
Llavors, perquè comencem amb una alpine
enlloc d’una python:3-alpine
per exemple?
Doncs perquè en aquesta activitat es tracta de que aprenguis com funciona Dockerfile 😤!
Elimina tots els contenidors:
$ docker rm -vf $(docker ps -aq)
Si volem podem executar un contenidor httpd
sense que s’executi apache 😱!
$ docker run --rm --name noapache -p 80:80 httpd echo "Bye Apache!"Bye Apache!$ docker ps -a | grep noapache$
Però no podem fer el mateix amb server
:
$ docker run --rm --name noserver -p 80:8000 server echo "Bye Server!"
A saber que està fent 🤨!
Obre un altre terminal i mira que està fent:
$ docker exec noserver psPID USER TIME COMMAND 1 root 0:00 python3 -m http.server 7 root 0:00 ps
Doncs ha executat python3 -m http.server
i passa del que li has dit que havia de fer com pots verificar al enviar una senyal d’interrupció, o sigui, Ctrl+c
.
$ docker run --rm --name noserver -p 80:8000 server echo "Bye Server!"^CServing HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Keyboard interrupt received, exiting.
Quan executem un contenidor podem sobreescriure CMD
escrivint les ordres que volem al final de tot de docker run
com hem fet amb httpd
.
Com que la imatge httpd
no defineix un ENTRYPOINT
, per defecte l’ENTRYPOINT
és /bin/sh -c
.
I com que el CMD
és:
$ docker inspect --type=image --format='{% raw %}{{json .Config.Cmd}}{% endraw %}' server["httpd-foreground"]
El que s’està executant per defecte és /bin/sh -c httpd-foreground
, a node ser que indiquis un CMD
diferent al executar docker run
.
Si volem també podem forçar a server
a fer el que volem amb l’opció --entrypoint
:
$ docker run --rm --name noserver -p 80:8000 --entrypoint /bin/sh server -c psPID USER TIME COMMAND 1 root 0:00 ps
Com has vist fins ara ENTRYPOINT
i CMD
estan pensats per utilitzar-los de manera conjunta, altre cosa es que després s’utilitzen com volen i els noms no ajuden gaire a saber exactament perqué serveixen.
En principi … 😂 :
ENTRYPOINT
. És el que sempre s’executarà.CMD
és el que l’usuari de la imatge pot parametritzar.
Pots veure que la imatge httpd
i moltres altres que has fet servir fins ara, això com que se la bufa 🙄.
La nostra imatge server
està ben dissenyada, és un servidor python que es pot parametritzar.
Per exemple pots dir que s’executi al port 80 enlloc del 8000:
$ docker run --rm --name server -p 80:80 server 80^CServing HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Keyboard interrupt received, exiting.
Però no fuciona … si vols pots buscar a ChatGPT o similar … o seguir llegint 👍.
El problema és que l’ENTRYPOINT
l’hem escript en la forma “shell command” enlloc de la forma “executable command” (més endavant explicarem la diferència).
Modifica el fitxer Dockerfile
:
FROM alpine:latestRUN apk update && apk add python3ENTRYPOINT ["python3", "-m", "http.server"]
I ara si que funciona, pots veure que el servidor escolta al port 80 enlloc del 8000:
$ docker run --rm --name server -p 80:80 server 80^CServing HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Keyboard interrupt received, exiting.
També pots canviar el directori arrel del servidor amb el flag -d
:
$ docker run --rm -d --name server -p 80:80 server 80 -d /usr
Des d’un altre terminal pots verificar amb lynx
que el servidor només mostar el contingut del directori /usr
del contenidor:
{% image “server-usr.png” %}
I per acabar la lliçó respecte CMD
, ¿Com podem utilitzar CMD
per millorar la imatge server
?
¿Que et sembla que per defecte escolti al port 80 enlloc del 8000?
FROM alpine:latestRUN apk update && apk add python3ENTRYPOINT ["python3", "-m", "http.server"]CMD ["80"]
Prova que per defecte funciona al port 80:
$ docker run --rm -d --name server -p 80:80 serverfc78af896ecff3e43b071be74430f819010c22ab57dc2c6d451f0daa07dae7cb$ lynx localhost$ docker stop server
I que com abans podem canviar el port:
$ docker run --rm server 3333^CServing HTTP on 0.0.0.0 port 3333 (http://0.0.0.0:3333/) ...
Keyboard interrupt received, exiting.
O la interfície a la que està escoltant:
$ docker run --rm server -b 127.0.0.1 3333^CServing HTTP on 127.0.0.1 port 3333 (http://127.0.0.1:3333/) ...
Keyboard interrupt received, exiting..
Activitat
Section titled “Activitat”Crea un fitxer Dockerfile.nmap
a partir de la imatge alpine
que per defecte faci un nmap
a localhost i que es pugui parametrizar amb opcions tal com es mostra a continuació.
$ docker build --tag nmap --file Dockerfile.nmap .
$ docker run --rm nmapNmap scan report for localhost (127.0.0.1)...
$ docker run --rm nmap xtec.devNmap scan report for xtec.dev (35.185.44.232)...
Pots veure que fer defecte docker build
utilitza el fitxer Dockerfile
, però que podem modificar aquest fitxer amb l’opció --file
.
{% sol %}
FROM alpine:latestRUN apk update && apk add nmapENTRYPOINT ["nmap"]CMD ["localhost"]
{% endsol %}
Shell vs Command
Section titled “Shell vs Command”Tots els tipus d’instruccions (ordres) que es passen al Docker Daemon es poden especificar en forma shell o exec.
Un conjunt d’instruccions en format shell inicien un procés que s’executa dins d’un shell: /bin/sh -c <command>
Per tant, té accés a les variables d’entorn.
El format que s’utilitza és:
<instruction> <command>
Crea un fitxer Dockerfile.shell
:
FROM alpine:latestENV name JordiENTRYPOINT /bin/echo "Welcome, $name"
Crea una nova imatge i executa un contenidor:
$ docker build --tag shell --file Dockerfile.shell ....$ docker run --rm shellWelcome, Jordi
Si no has de processar variables d’entorn és millor utilitzar el format exec que no utilitza un shell i evites el procés de validació i processament del shell.
També és necessari quan vols utilitzar a la vegada l’ENTRYPOINT
i CMD
tal com hem vist abans.
El format que s’utilitza és:
<instruction> ["executable", "parameter1", "parameter2", ...]
Crea un fitxer Dockerfile.exec
:
FROM alpine:latestENV name JordiENTRYPOINT ["/bin/echo", "Welcome, $name" ]
Crea una nova imatge i executa un contenidor:
$ docker build --tag exec --file Dockerfile.exec .$ docker run --rm execWelcome, $name
Pots veure que aquest cop la variable d’entorn name
no es processa.
Però això vol dir que no podem executar un procés mitjançant el shell amb la forma exec?
Un si, o un no, no és una resposta vàlida 😤!
Això és una resposta:
FROM alpine:latestENV name JordiENTRYPOINT ["/bin/sh", "-c", "echo Welcome, $name" ]
I 👍 …
$ docker build --tag exec --file Dockerfile.exec .$ docker run --rm execWelcome, Jordi
Referències
Section titled “Referències”El contingut d'aquest lloc web té llicència CC BY-NC-ND 4.0.
©2022-2025 xtec.dev