Docker té dues opcions perquè els contenidors emmagatzemin fitxers a la màquina amfitrió, de manera que els fitxers es mantinguin fins i tot després que el contenidor s'aturi: volums i muntatges d'enllaç.

Introducció

Arbre de fitxers

Si vols començar amb un entorn net, esborra-ho tot:

$ docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
...

Total reclaimed space: 27.96MB

A diferència d'altres sistemes operatius, Linux unifica tot l'emmagatzematge en un sol arbre.

A Windows tens C:\ , D:\ , etc.:

Però, en Linux tot està muntat a /.

Punt de muntatge

Els dispositius d'emmagatzematge, com ara particions de disc o particions de disc USB, estan connectats a ubicacions específiques d'aquest arbre.

Aquests llocs s'anomenen punts de muntatge.

Un punt de muntatge defineix la ubicació a l'arbre, les propietats d'accés a les dades en aquest punt (per exemple, l'escriptura) i la font de les dades muntades en aquest punt (per exemple, un disc dur, un dispositiu USB o un disc virtual en memòria).

Aquí tens un dibuix que representa un sistema de fitxers construït a partir de diversos dispositius d'emmagatzematge, amb cada dispositiu muntat en una ubicació i nivell d'accés específics.

RAM disk

Crear un sistema de fitxers en RAM permet que no quedi rastre de fitxers confidencials temporals o utilitzar fitxers temporals amb un accés super ràpid.

Crea un directori i permet que tothom tingui accés:

$ sudo mkdir /var/fast
$ sudo chmod 777 /var/fast

Ara crea un sistema de fitxers tmpfs de 1024 MB i nom de dispositiu ramdisk:

$ sudo mount -t tmpfs -o size=1024m ramdisk /var/fast

Pots veure que la unitat ramdisk s’ha muntat correctament:

$ mount | grep ramdisk
ramdisk on /var/fast type tmpfs (rw,relatime,size=1048576k,inode64)

Prova de crear un fitxer, veuras que no notaras la diferencia:

$ echo "Hello" > /var/fast/message.txt
$ ls -l /var/fast/
total 4
-rw-rw-r-- 1 isard isard 6 de maig  21 15:34 message.txt

Si desmuntes la carpeta, perds tota la informació perquè no estava guardada en un dispositiu físic sinó en memòria.

$ sudo umount /var/fast
$ ls -l /var/fast/
total 0

Contenidor

Els punts de muntatge permeten que el programari i els usuaris utilitzin l'arbre de fitxers en un entorn Linux sense saber exactament com s'assigna aquest arbre a dispositius d'emmagatzematge específics.

Cada contenidor té un MTN namespace i una arrel única d'arbre de fitxers.

L’arbre de fitxers d’un contenidor està format per un conjunt de punts de muntatge de 4 tipus diferents:

  1. ImageFS – Les imatges que s’han descarregat es munten en diferents punts del sistema de fitxers del contenidor.

  2. In-memory storage – Un disc RAM com el que hem vist abans

  3. Bind mounts – Un directori concret del host

  4. Docker Volumes – Un directori lògic del host

A l’esquerra està el sistema de fitxers vist des del contenidor, i a la dreta els punts de muntatge reals en sistema de fitxers del host:

Bind mount

Un bind mount et permet muntar un directori del host en qualsevol punt de muntatge del sistema de fitxers del contenidor.

Arrenca un contenidor Apache amb l’opció -p 80:80 perquè poguem connectar-nos des de fora del host:

$ docker run --rm -d --name apache -p 80:80 httpd
...
97d2d3f55150795b03ae2a3131ee3cfd469dbfc82e6f793c514853cc73da662b
$ curl localhost
<html><body><h1>It works!</h1></body></html>

A Docker - Xarxa ja explicarem com funciona l’opció -p.

Si volem canviar el missatge, podem entrar dins el contenidor i modificar el fitxer index.html:

$ docker exec -it apache bash
...$ echo "Hello!" > htdocs/index.html 
...$ exit
   exit
$ curl localhost
Hello!

Una altra opció és crear una carpeta www fora del contenidor amb un fitxer index.html i altre contingut.

$ mkdir www
$ echo "Bye!" > www/index.html

Eliminem el contenidor que està en execució:

$ docker stop apache
apache

Creem un contenidor nou montant el directori /usr/local/apache2/htdocs a la nostra carpeta www.

$ docker run --rm -d --name apache -p 80:80 --mount type=bind,source="$(pwd)"/www,target=/usr/local/apache2/htdocs httpd
9dacc8d3d17cd2a4347732ca785e4a8f57fe2031b3baaae96a3399c14c9985c9

Si fas un curl a localhost pots veure que ara respon amb el teu fitxer index.html no amb el del contenidor:

$ curl localhost
Bye!

Modifica el fitxer index.html i verifica que el servidor apache contesta amb el nou contingut:

$ echo "Patufet"> www/index.html 
$ curl localhost
Patufet

Entra dins el contenidor, modifica el fitxer index.html, surt del contenidor i verifica que desde fora també s'ha modificat:

$ docker exec -it apache bash
...$ cat htdocs/index.html 
   Patufet
...$ echo "Patufet" > htdocs/index.html 
...$ exit 
$ cat www/index.html 
Patufet

Amb l’ordre docker inspect pots verificar en la secció Mount que el muntatge d’enllaç s'ha creat correctament:

$ docker inspect apache --format '{{ json .Mounts }}' | jq
[
  {
    "Type": "bind",
    "Source": "/home/isard/www",
    "Destination": "/usr/local/apache2/htdocs",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  }
]

Com que hem separat el contingut web del servidor web podem crear un altre servidor Apache que escolti al port 8080 i serveixi el mateix contingut:

$ docker run --rm -d --name apache2 -p 8080:80 --mount type=bind,source="$(pwd)"/www,target=/usr/local/apache2/htdocs httpd
292f7b72277524fc656593b9504676e782e50e7d407eff6686155376ae699082
$ curl localhost:80
Patufet
$ curl localhost:8080
Patufet

També podem crear un altre servidor Nginx que escolti al port 3000 i serveixi el mateix contingut.

L’única diferència és que la carpeta www la tindrem que montar a /usr/share/nginx/html:

$ docker run --rm -d --name nginx -p 3000:80 --mount type=bind,source="$(pwd)"/www,target=/usr/share/nginx/html nginx
...
c1f360dbc7e2ca97ca74648441f6a62bcadaf029954dfb23bf63361993df0e4a
$ curl localhost:3000
Patufet

Pots veure que hi ha tres servidors web servint el mateix contingut en ports diferents, i tots tenen el contingut de la carpeta www on ells volen:

$ docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
CONTAINER ID   NAMES     PORTS
20c8ebb1885d   nginx     0.0.0.0:3000->80/tcp, :::3000->80/tcp
292f7b722775   apache2   0.0.0.0:8080->80/tcp, :::8080->80/tcp
9dacc8d3d17c   apache    0.0.0.0:80->80/tcp, :::80->80/tcp

A part del contingut també pots montar els fitxer de configuració, d’aquesta manera ens separat el que és teu (estat) del programa que s’ha d’executar (comportament).

Eliminem els contenidors i ens quedem amb l’únic que ens importa: la carpeta www:

$ docker stop apache apache2 nginx
apache
apache2
nginx

Emmagatzematge en memòria

La majoria d’aplicacions web utilitzen fitxers de claus privades, contrasenyes de bases de dades, fitxers de claus API o altres fitxers de configuració amb informació sensible.

És millor guardar aquesta informació en memòria perquè no deixa rastre.

Creem un contenidor amb un punt de muntatge en memòria:

$ docker run -it --name memory --mount type=tmpfs,dst=/secret alpine
...$ echo "P@ssw0rd" > /secret/password.txt
...$ echo "Hello" > /hello.txt
...$ exit

Si aturem el contenidor i el tornem a arrencar, l’arxiu password.txt haurà desaparegut mentres que el fitxer hello.txt encara estarà:

$ docker stop memory
memory
$ docker start memory
memory
$ docker exec -it memory sh
... $ cat /hello.txt 
    Hello
... $ cat /secret/password.txt
    cat: can't open '/secret/password.txt': No such file or directory

Volums

Enlloc de crear un carpeta i tenir que dir la ruta on està, pots utilitzar carpetes lògiques que es poden referenciar amb un nom.

Per exemple, quan executes una wiki en un contenidor t’interessa conservar les dades quan elimines el contenidor.

Enlloc de crear una carpeta pots crear un volum i guardar les dades allà.

Imagina’t que vols crear una wiki amb dokuwiki.

El primer pas NO es instal.lar dokuwiki com faries normalment, sinó crear un emmagatzematge on es guarda la wiki.

Crea un volum wiki-nature:

$ docker volume create wiki-nature
wiki-nature
$ docker volume ls
DRIVER    VOLUME NAME
local     wiki-nature
$ docker volume inspect --format "{{ json .Mountpoint }}" wiki-nature 
"/var/lib/docker/volumes/wiki-nature/_data"

Pots veure que el volum és local (està en una carpeta local), concretament a /var/lib/docker/volumes/wiki.

Per defecte, un volum és crea en el sistema de fitxers del host, però hi ha opcions més avançades.

Ha continuació has d’arrencar un contenidor dokuwiki amb el volum que has creat perquè totes les pàgines de la wiki s’escriguin en aquest volum.

$ docker run --rm -d --name wiki -p 80:8080 --volume wiki-nature:/bitnami/dokuwiki bitnami/dokuwiki
...
f11acc644df8f1a2f985be03be66d75766c17f1d15b86d04f2a1fb7a2f85fbf7

Obre un navegador a http://localhost i edita la pàgina principal:

Aquí tens una explicació de com pots utilitzar la imatge: https://hub.docker.com/r/bitnami/dokuwiki/dockerfile/.

  1. Edita la primera pàgina amb algun contingut.

  2. Para el contenidor (aquest s’eliminarà perquè has fet servir l’opció --rm)

$ docker stop wiki
wiki
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND     CREATED          STATUS          PORTS     NAMES
ad7832d4d2c8   alpine    "/bin/sh"   24 minutes ago   Up 20 minutes             memory
  1. Pots verificar que les pàgines estan guardades al volum i no es perden.
$ sudo ls /var/lib/docker/volumes/wiki-nature/_data
  1. Si vols torna a veure o editar la wiki només has de tornar a arrencar un altre contenidor dokuwiki muntant el volum:
$ docker run --rm -d --name wiki -p 80:8080 --volume wiki-nature:/bitnami/dokuwiki bitnami/dokuwiki
c9794ef60a22dac1e2a35dce8cc1285b68b499a0b925d541c86e0d2d4dfcdb22

Pots veure que el contenidor té un ID diferent i que el contingut s'ha guardat:

Activitat

  1. Crea un volum wiki-football i genera contingut creant un contenidor.
$ docker volume create wiki-football
$ docker run --rm -d --name wiki-football -p 90:8080 --volume wiki-football:/bitnami/dokuwiki bitnami/dokuwiki
a12f77e9b2d9fcf93e1d0cc4502869386fad1fdad1656af15470a8ab3e1a1908

Pots veure que:

  1. Pots tenir dos dokuwiki funcionant a la vegada en ports diferents.

  2. No has de tenir instal.lat dokuwiki o tenir contenidors funcionat si no els necessites en aquell moment (tot el contingut està guardat en volums).

$ docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
CONTAINER ID   NAMES           PORTS
a12f77e9b2d9   wiki-football   8443/tcp, 0.0.0.0:90->8080/tcp, :::90->8080/tcp
c9794ef60a22   wiki            8443/tcp, 0.0.0.0:80->8080/tcp, :::80->8080/tcp
$ docker volume ls
DRIVER    VOLUME NAME
local     wiki-football
local     wiki-nature

Neteja de volums

Com has vist abans pots veure tots els volums que tens en el sistema amb l'ordre docker volume ls:

$ docker volume ls
DRIVER    VOLUME NAME
local     wiki-football
local     wiki-nature

Per suprimir un volum has d’executar l’ordre docker volume rm:

$ docker volume rm wiki-nature
Error response from daemon: remove wiki-nature: volume is in use - [c9794ef60a22dac1e2a35dce8cc1285b68b499a0b925d541c86e0d2d4dfcdb22]

Però primer has d'aturar el contenidor:

$ docker stop wiki
wiki
$ docker volume rm wiki-nature
wiki-nature

Activitats

Compartir fitxers

Si tenim un conjunt de fitxers html que volem fer servir mitjançant un servidor web, només volem un servidor web que funcioni i res més.

Per tant, enlloc d’executar un contenidor ubuntu i instal.lar un servidor apache, farem servir un contenidor amb una versió concreta d’apache ja instal.lat.

Una de les capacitats més rellevants d’un contenidor, es que podem executar un servidor apache sense haver de insta.lar res en el nostre host, i eliminar-lo quan ja no el necessitem. Pert tant, afegim l’opció –rm perquè s’elimini quan el parem.

Això si, hem de permetre que es pugui accedir al servidor apache desde fora del contenidor fent servir un port forward a la xarxa virtual privada on està el contenidor:

$ docker run --rm -d --name apache -p 8000:80 httpd
ad5087500ed6abe77f6226bac5ef6693c8f71b8e2aaf3b0ce8e92a498417109a
$ curl localhost:8000
<html><body><h1>It works!</h1></body></html

Pots veure que tens accés al servidor web des del host en el port 8000.

El primer que pots observar, és que la pàgina per defecte d’apache és molt senzilla: It works!.

Si vols canviar el contingut de la pàgina pots entrar dins el contenidor i modificar la pàgina:

$ docker exec -it apache bash
... $ echo "Hello World!" > htdocs/index.html 
... $ exit
    exit
$ curl localhost:8000
Hello World!

Però, quan el contenidor s’aturi i s’elimini es perdrà tot … Que hem de fer?

Una de les maneres de fer servir un contenidor apache és deixar que el contenidor tingui accés a la carpeta on tenim el nostre lloc web.

Atura el servidor apache:

$ docker stop apache
apache
$ curl localhost:8000
curl: (7) Failed to connect to localhost port 8000 after 0 ms: Connection refused

Crea el teu lloc web al host: site

$ mkdir site
$ echo "Estic fora" > site/index.html

Aquesta vegada arrenca apache montant el directori /usr/local/apache2/htdocs/ del contenidor a la teva carpeta site en mode només lectura:

$ docker run --rm -d --name apache -p 8000:80 --mount type=bind,src="$(pwd)"/site,dst=/usr/local/apache2/htdocs,readonly=true httpd
2cde52ea8c9087bd9fbee769696e447a022044b0cb3bd55b462a8605636a2e43
$ curl localhost:8000
Estic fora

Si modifiques el contingut del fitxer site/index.html del host el servidor respondrà amb el nou fitxer:

$ echo "Encara estic fora :-)" > site/index.html 
$ curl localhost:8000
Encara estic fora :-)

Com és possible? Si entres al contenidor veurás que encara que el contenidor superposi els fitxers de la imatge httpd, la nostra comanda superposa la carpeta site a la del contenidor, i el resultat és que ja no té accés al fitxer index.html que està a la imatge, sinò al de la nostra carpeta.

$ docker exec -it apache bash
... $ cat htdocs/index.html 
    Encara estic fora :-)
... $ exit
    exit

I si proves de modificar el contingut del fitxer index.html no pots, perquè has montat la carpeta site amb permisos de només readonly.

$ docker exec -it apache bash
... $ echo "Estic dins" > htdocs/index.html 
    bash: htdocs/index.html: Read-only file system

Com pots veure ja no estas sotmès a la dictadura d’apache o nginx: Tens el teu site al lloc que tu vols.

Versió

Quan executes un contenidor a partir d’una imatge pots dir exactament quina versió vols executar.

Per tant, a més de poder dir exactament la versió d’apache que vull, puc tenir vàries versions a la vegada en ports diferents i servint el mateix lloc web!

A https://hub.docker.com/_/httpd pots trobar totes les versions.

Què tal la 2.2.34 del 2017?

$ docker run --rm -d --name apache-old -p 8001:80 --mount type=bind,src="$(pwd)"/site,dst=/usr/local/apache2/htdocs,readonly=true httpd:2.2.34

I perquè no fas servir nginx?

$ docker run --rm -d --name nginx -p 8002:80 --mount type=bind,src="$(pwd)"/site,dst=/usr/share/nginx/html,readonly=true nginx

Fixa’t que nginx fa servir un directori diferent a apache.

Tens 3 servidors web servint el mateix contingut a ports diferents.

$ curl localhost:8000
Encara estic fora :-)
$ curl localhost:8001
Encara estic fora :-)
$ curl localhost:8002
Encara estic fora :-)

Certificats

Si no saps com funcionen els certificats mira aquesta activitat: Certificats

Apache

Crea una carpeta server i dins de la carpeta baixa l’script apache.sh: https://gitlab.com/xtec/smx-6/-/raw/main/certs/apache.sh.

$ curl https://gitlab.com/xtec/smx-6/-/raw/main/certs/apache.sh -o apache.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1570  100  1570    0     0   3363      0 --:--:-- --:--:-- --:--:--  3369

Aquest script executa un servidor apache amb un certificat autofirmat en els ports 8080 (HTTP) i 8443 (HTTPS).

Atenció! L'script crea un contenidor amb el nom apache, i no pot haver cap contenidor amb aquest nom.

Executa l’script i mira el contingut que s’ha generat:

$ chmod u+x apache.sh 
$ ./apache.sh
...
a423ee4e93007fa64220b1e638d368c13fdcd55ee000325a4b4c90d2d5ed7b13
$ ls
apache.sh  certs  html  httpd-ssl.conf  httpd.conf  site

En aquest cas, a més del lloc web en la carpeta html, tenim uns arxius de configuració externs al contenidor que es munten en punts concrets per modificar el comportament del servidor.

La carpeta certs té els certificats, la carpeta html el contingut, i httpd-ssl.conf i httpd.conf són fitxers de configuració.

I el que és més important, tenim agrupats els únics fitxers que són necessaris per configurar un apache amb connexió HTTPS on nosaltres volem.

Pots verificar que el servidor funciona correctament, amb redirecció del port 8080 (HTTP) al ports 8443 (HTTPS):

$ curl -I localhost:8080
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>307 Temporary Redirect</title>
</head><body>
<h1>Temporary Redirect</h1>
<p>The document has moved <a href="https://localhost:8443/">here</a>.</p>
</body></html>

Si mires les capçaleres HTTP pots veure que el servidor torna un resposta 307 Temporary Redirect:

$ curl -I localhost:8080
HTTP/1.1 307 Temporary Redirect
Date: Tue, 21 May 2024 21:04:38 GMT
Server: Apache/2.4.59 (Unix) OpenSSL/3.0.11
Location: https://localhost:8443/
Content-Type: text/html; charset=iso-8859-1

Executa curl amb les opcions -L (segueix el redirect) i -k (no valida el certificat), i verifica que funciona:

$ curl -L -k localhost:8080
<p>Add content to html folder</p>

Atura (stop) el contenidor apache.

Obre l’script apache.sh, i mira que hi fa per entendre que hauries de fer tu manualment.

Nginx

A continuació baixa aquest script que executa un servidor nginx amb un certificat autofirmat en els ports 8080 (HTTP) i 8443 (HTTPS): https://gitlab.com/xtec/smx-6/-/raw/main/certs/nginx.sh.

Executa l’script.

Si fas un ls pots veure que l’única diferència que hi ha respecte apache, és que nginx necessita el seu propi fitxer de configuració nginx.conf, la resta és el mateix.

$ ls 
apache.sh  certs  html  httpd-ssl.conf  httpd.conf  nginx.conf  nginx.sh  site

Pots verificar que funciona igual que apache:

$ curl -L -k localhost:8080
<p>Add content to html folder</p>

D’aquesta manera tens separat el que és particular del que és genèrica.

Pots passar de nginx a apache en un moment.

Obre l’script nginx.sh i mira que fa per entendre que hauries de fer tu manualment.

Còpies

Pots realitzar còpies del host al contenidor i a la inversa amb l’ordre docker cp:

Crea un servidor web caddy amb un volum amb la mateixa ordre:

$ docker run --rm -d --name caddy -p 80:80 -v caddy-data:/data caddy
19fdc78f78b9eeb8feee71ec9535676fc125fc89efcc744c546710c274d1882a
$ docker volume ls | grep caddy
local     caddy-data

Verifica que el servidor funciona:

$ curl -s localhost | head -n 10
<!DOCTYPE html>
<html>
<head>
        <title>Caddy works!</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="icon" href="data:,">
        <style>
                * {
                        box-sizing: border-box;

Enlloc de muntar una carpeta o entra dins el servidor per modificar el fitxer index.html podem crear un fitxer (si no el tinc creat) i copiar-lo al servidor:

$ echo "Hello Caddy!" > index.html
$ docker cp index.html  caddy:/usr/share/caddy/index.html
Successfully copied 2.05kB to caddy:/usr/share/caddy/index.html
$ curl localhost
Hello Caddy!

També es pot copiar del contenidor al servidor, i fer còpies recursives.

Volums

Si vull copiar el contingut d’un volum, primer l’haig de montar en un contenidor.

Si el volum és local, pots accedir directament al directori amb sudo, però els contenidors poden estar en altres dispositius mitjançant btrfs, ZFS, etc.

Per tant, has de muntar el volum en un contenidor.

Montem el volum wiki en un contenidor alpine:

$ docker run -d --rm --name wiki -v wiki-nature:/wiki alpine tail -f /dev/null
8b56c20ad2b6badc5b33e8aeab28397ec280fcc41f5817aa21cec51a619fe304

Ja pots copiar del contenidor:

$ docker cp wiki:/wiki/data/pages/wiki .
Successfully copied 29.7kB to /home/david/docker/.
$ ls wiki
dokuwiki.txt  syntax.txt  welcome.txt

Referències