Este es uno de los capítulos del tutorial Tutorial de Docker. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.
En los capítulos anteriores de este tutorial viste como gestionar imágenes y como gestionar contenedores. Esto, era preciso para poder controlar lo que haces con docker, sin llenar tu equipo ni de imágenes ni contenedores que nunca utilizas, o de varios contenedores que están corriendo sin control. Pero, ha llegado ya el momento, de que empieces a lanzar contenedores como si no hubiera un mañana. Y para esto, te propongo hacerlo paso a paso, y siguiendo un ejemplo sencillo. Comenzando por lo mas sencillo para terminar por lo mas complejo. Pero básicamente, el objetivo de este capítulo del tutorial es ejecutar contenedores con docker.
Vamos allá
Ejecutar contenedores docker
Un pequeño consejo…
Si quieres una buena ayuda para trabajar con docker, te recomiendo que utilices Bash-it. Si no sabes lo que es, te recomiendo también la lectura del artículo sobre potenciar el poder del terminal, y activa los alias para docker.
El contenedor de entrenamiento
Mi propuesta para este capítulo, es partir de un contenedor de Nginx. Un cotenedor con el que probar esto de ejecutar contenedores docker. Se trata de partir de una imagen de Nginx sobre Alpine. De esta manera te vas a asegurar tener tanto una imagen como un contenedor de reducidas dimensiones.
Pero antes de esto aclarar que es Alpine, Nginx, y que es lo que tendremos al final de todo este proceso.
Alpine
Alpine Linux es una distribución Linux basada en musl y BusyBox, diseñada pensando en la seguridad, simplicidad y la eficiencia de recursos. En este sentido, imagina que el peso de la imagen de docker apenas supera los 5 MB. ¿Que mas se puede pedir en cuanto a ligereza?.
Nginx
Si eres un oyente o lector asiduo de este sitio, seguro que Nginx te resulta conocido. Lo cierto que en los últimos tiempos lo he utilizado para casi cualquier cosa que puedas imaginar, desde un servidor web hasta un proxy inverso.
Si no lo conoces y quieres comenzar a probar con él, te recomiendo una lectura al capítulo del tutorial sobre Raspberry, primeros pasos, en el que te explico como instalar una infraestructura LEMP con Nginx.
Cuando tengas el contenedor en funcionamiento vas a tener un sencillo servidor de páginas web que vas a poder utilizar para mostrar lo que quieras, o necesites. Y poner ese servidor en funcionamiento, será igual de sencillo que cualquier otro servicio que quieras. Tan solo se tratará de ejecutar contenedores docker.
La imagen
La imagen de Nginx con Alpine esta disponible en el repositorio oficial, con lo que tenerla en tu equipo y crear tu primer contenedor es terriblemente sencillo. Igual que para cualquier otro servicio, tan solo ejecutar contenedores. Pero, no te preocupes que se irá complicando poco a poco.
Paso a paso
Ejecutando el contenedor sin mas…
Llegó el momento. Como primer paso, ejecuta el contenedor sin mas. Para ello, simplemente tienes que ejecutar la siguiente instrucción,
$ docker run -d --name test01 nginx:alpine
Algunas observaciones,
- Como es una imagen oficial observarás que no es el repositorio típico
usuario/nombre_imagen
. - Lo segundo que habrás notado es la etiqueta
:alpine
. En el repositorio denginx
, existen multitud de imágenes. Y no solo por versión de Nginx, también por los acompañamientos que lleve, por ejemplo Perl, o Alpine o Alpine y Perl, y mucho mas. - La opción
-d
es para que el contenedor se ejecute en segundo plano. De otra forma, si no incluyes esta opción, el terminal se detendrá en este punto, y no podrás interactuar con él. - La opción
--name
no es necesaria, al igual que la anterior, pero es realmente cómoda, porque te permite llamar al contenedor de tu a tu. Es mucho mas sencillo indicar el nombre del contenedor para trabajar con él que su código identificador.
Para conocer el peso de esta imagen, ejecuta,
docker images | grep nginx
Verás que apenas supera los 21 MB. Si quieres tener mas información, ejecuta docker history nginx:alpine
. El resultado que verás es el siguiente,
MAGE CREATED CREATED BY SIZE COMMENT
d87c83ec7a66 3 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 3 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 3 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 weeks ago /bin/sh -c set -x && addgroup -g 101 -S … 15.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.3.5 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 5 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 5 weeks ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
Si te fijas, la última posición, aunque en realidad es la primera, se corresponde con Alpine, y supera ligeramente los 5.5 MB. Por otro lado, el peso de Nginx es de 15.6MB.
Si ahora ejecutas docker ps
, te aparecerá la ejecución de tu contenedor. Sin embargo no puedes hacer nada con él. A menos, que entremos en el interior de ese contenedor y demos un vistazo. Para entrar en el interior de ese contenedor, la instrucción a ejecutar es,
docker exec -it test01 sh
Una vez en el interior del contenedor, simplemente, ejecuta ps -ef
, y verás que hay muy pocos procesos. En particular,
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
8 nginx 0:00 nginx: worker process
9 nginx 0:00 nginx: worker process
10 nginx 0:00 nginx: worker process
11 nginx 0:00 nginx: worker process
17 root 0:00 sh
26 root 0:00 ps -ef
Sin embargo, no podemos ver Nginx en funcionamiento, con lo que, realmente, este contenedor que tenemos ahora no nos sirve para gran cosa.
Exponiendo puertos
Dado que el contenedor anterior, como has visto, no te es de gran utilidad, detenlo y bórralo. Para esto, tienes que aprovechar que le has puesto nombre, recuerda test01
. Para ello, ejecuta estas dos instrucciones,
docker stop test01
docker rm test01
Para poder poder ver lo que sirve Nginx, tienes que conectar el puerto del contenedor con el puerto de tu equipo. Para eso, la instrucción que vas a ejecutar es,
docker run -d --name test01 -p 81:80 nginx:alpine
En este caso, la opción -p 81:80
indica que cuando te conectas al puerto 81 de tu equipo, estás conectandote al puerto 80 del contenedor. Así, ahora, simplemente inicia el navegador y escribe la dirección https://localhost:81
.
¿porque he puesto el puerto 81? Simplemente, para que tengas claro quien es quien. Es decir, para que veas cual es el puerto del equipo y cual el del contenedor. Si quieres, para el contenedor, bórralo y ejecuta un contenedor, pero esta vez con -p 80:80
. Verás que ahora tan solo tienes que excribir https://localhost
.
Por supuesto, puedes exponer mas de un puerto. Por ejemplo, si quieres tener una conexión segura, puedes exponer en puerto 443
. Para ello, ejecuta la siguiente instrucción,
docker stop test01
docker rm test01
docker run -d --name test01 -p 80:80 -p 443:443 nginx:alpine
Ahora ya puedes conectarte tanto al puerto 80
como al puerto 443
. Simplemente tienes que escrbir en tu navegador https://localhost
o https://localhost
. Sin embargo, en el segundo caso te pondrá un mensaje de error indicando que la conexión segura ha fallado.
Para resolver el problema de la conexión segura es necesario contar con certificados. Para este tutorial, partiré de resolverlo con certificados autofirmados. Esto realmente no es necesario, porque podríamos hacerlo con Let’s Encrypt, pero esto, lo haremos en otra ocasión.
Así, te ofrezco dos soluciones, una que es no deseable, y a continuación el procedimiento mas adecuado–
La solución no deseable
Esto que te voy a comentar a continuación es justo lo que no tienes que hacer, para resolver el problema de la conexión segura, o cualquier otro problema, utilizando el mismo procedimiento.
Para resolver este problema, vas a generar los certificados autofirmados en tu equipo, para posteriormente copiarlos al contenedor. Para hacer esto, ejecuta las siguientes instrucciones en un terminal en tu equipo, no en el contenedor,
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx-selfsigned.key -out nginx-selfsigned.crt
openssl dhparam -out dhparam.pem 2048
A continuación los copias al contenedor,
docker cp nginx-selfsigned.crt test01:/
docker cp nginx-selfsigned.key test01:/
docker cp dhparam.pem test01:
Entra en el contenedor ejecutando docker exec -it test01 sh
. Una vez dentro del contenedor, crea los directorios para los certificados y copialos en esos directorios, conforme puedes ver en las siguientes líneas,
mkdir -p /etc/ssl/certs/
mkdir -p /etc/ssl/private/
mv /nginx-selfsigned.crt /etc/ssl/certs/
mv /nginx-selfsigned.key /etc/ssl/private/
mv dhparam.pem /etc/ssl/certs/
Una vez ya tienes esto, tienes que modificar la configuración de Nginx, para esto, tienes dos opciones, o bien, la editas directamente en el contenedor, o bien, la editas en tu equipo y la copias. Voy a optar por editarla en el equipo.
Para editarla en el contenedor, necesitas instalar un editor, porque no hay ninguno por defecto. Para instalarlo, simplemente ejecuta la siguiente instrucción,
apk add nano
Ya tienes instalado nano
, ahora edita la configuración de Nginx, ejecutando,
nano /etc/nginx/conf.d/default.conf
A continuación, te dejo el contenido de la configuración que yo he utilizado,
server {
listen 80;
listen 443 http2 ssl;
listen [::]:443 http2 ssl;
server_name localhost;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Ya lo tienes todo. Los siguientes pasos, son salir del contenedor, pararlo y arrancarlo. Ojo, que tienes que arrancar este contenedor,
docker stop test01
docker start test01
Si todo ha ido bien, deberías acceder a https://localhost/
. Pero, te habrás dado cuenta del trabajo.
Guardando los cambios
Ahora que ya has realizado todos los cambios, no estaría nada mal poder guardarlos. Efectivamente, tienes la posibilidad de guardarlos en forma de imagen, de forma que la próxima que necesites un contenedor de Nginx con certificado no tengas que hacerlo de nuevo.
Para crear esa imagen, simplmente ejecuta, la siguiente orden,
docker commit test01 atareao/nginx:autocertificado
Ahora, lista tus imágenes, y encotrarás esta nueva imagen. Y, no estaría nada mal, que la tuvieras disponible desde un repositorio, ¿no?. Pues esto lo podrás ver en un capítulo posterior dedicado en exclusiva a Docker Hub.
Exponiendo volúmenes
Esto es una auténtica locura. Cada vez que quieras modificar la configuración de Nginx, o cada ves que quieras cambiar los certificados tendrás que modificar la imagen. Es mas, todavía voy un paso mas allá. Cada vez que quieras cambiar la web, vas a tener que cambiar la imagen. Esto no es ni productivo ni mucho menos. Es necesario buscar otra solución.
Y por supuesto que la hay. La solución es algo similar a lo que hiciste con los puertos, pero esta vez con directorios. Me explico. Se trata de engañar al contenedor, haciéndole pensar que un directorio de tu equipo es un directorio del contenedor. Así, por ejemplo, podemos decirle que el directorio de tu equipo /home/lorenzo/docker/conf
es el directorio /etc/nginx/conf.d
del contenedor. De esta sencilla manera, cuando busque en el directorio del contenedor la configuración de Nginx, realmente la estará buscando en tu equipo.
De esta forma, de una manera realmente sencilla, habrás solucionado el problema de modificar la configuración sin necesidad de modificar ni el contenedor ni la imagen. Ya tienes todo el problema resuelto.
Así por ejemplo, para el ejemplo en cuestión, podemos ejecutar la siguiente instrucción que nos levantará el docker con los directorios de configuración y el de la página web en nuestro local,
docker run \
-d -p 80:80 \
-p 443:443 \
-v "$(pwd)"/crt:/etc/ssl/certs \
-v "$(pwd)"/conf:/etc/nginx/conf.d \
-v "$(pwd)"/web:/var/www/html \
--name test01 atareao/nginx:autocertificado
En este caso, he realizado algunas modificaciones para el archivo de configuración de Nginx. Por supuesto en local, sin necesidad de la locura que había hecho anteriormente. La nueva configuración es la que puedes ver a continuación, y que iría dentro del archivo /etc/nginx/conf.d/default.conf
server {
listen 80;
listen 443 http2 ssl;
listen [::]:443 http2 ssl;
server_name localhost;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/certs/nginx-selfsigned.key;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
location / {
root /var/www/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
En el directorio crt
he puesto los certificados, en el directorio conf
la configuración de Nginx, y por último, en el directorio web
he dejado el contenido de la web.
Simplemente espectacular
Conclusión
Desde luego que la parte en la que explico como no utilizar docker, no la tienes que repetir. Se trata de que vieras lo complicado que se puede volver hacer una imagen, y lo sencillo que es utilizar la asignación tanto de puertos como de volúmenes a la hora de ejecutar contenedores.
Fíjate que ahora, con una sola línea, tienes montado el servidor Nginx, con la ventaja de que todas las modificaciones que haces en local se verán reflejadas en el servidor. Una auténtica maravilla.
De esta forma, si eres desarrollador web, no tienes que levantar ni un XAMP, ni un LAMP ni u LEMP, ni nada de nada. Con una sola línea de terminal, es decir, con ejecutar contenedores, tienes preparado tu servidor para empezar a trabajar. Con la increíble ventaja de que esta imagen, la puedes subir a los repositorios de Docker Hub, y desde allí descargarla donde la necesites utilizar, con la garantía de que funcionará.
Más información,
Imagen de Erich Westendarp en Pixabay
Buenas tardes
Ante todo muchas gracias por su aporte.
Yo tengo una salva de una imagen local y un contenedor local osea lo salve desde otra pc, la cuestion es la siguiente como yo importo correctamente hacia mi docker la imagen o el contenedor, porque cuando importo la imagen me pone en el apartado del REPOSITORY: none y en el TAG: none y no entiendo por que, yo utilizo el comando load para cargar importar las imagenes o cargar las imagenes no entiendo porqué no me corre cuando le doy docker start y el ID de la imagen, a la verdad estoy perdido. y necesito ayuda. Yo estoy usando debian 10
Buenos días grupo, estoy realizando el tutorial de contenedores de atareao. Capítulo 3, donde no realice el docker commit test01. Estoy practicando con el mismo ejercicio me sale este error |
docker run -d –name test01 -p 80:80 ngnix:alpine
Unable to find image ‘ngnix:alpine’ locally
docker: Error response from daemon: pull access denied for ngnix, repository does not exist or may require ‘docker login’: denied: requested access to the resource is denied.
See ‘docker run –help’.
Mi consulta es como puedo volver a practicar ? Con solo renombrar es suficiente ? Desde ya muchas gracias