Capitulo 2. Crear y compartir imágenes

2.0 Introducción

Después de trabajar con las operaciones básicas en Docker, en este capítulo veremos como crear y compartir nuestras propias imágenes. Nos puede interesar el empaquetado de una aplicación existente, o bien construír una nueva desde cero.

Cuando creamos un contenedor a partir de una imagen, y realizamos cambios en el mismo, a continuación, podemos realizar un ‘commit’ para crear una nueva imagen personalizada con los cambios realizados. Aunque este proceso no es facilmente reproducible, por lo que se recomienda el uso de un fichero Dockerfile.

Para poder compartir nuestras imágenes con otros usuarios, debemos hacer uso de un repositorio compartido, por ejemplo, Docker Hub. Veremos como hacer uso de este servicio, así como automatizar su despliegue desde Github o Bitbucket.

2.1 Guardar los cambios de un contenedor en una nueva imagen

Creamos un contenedor interactivo y actualizamos la base de datos de paquetes:

$ docker run -ti ubuntu:14.04 /bin/bash
root@id:/# apt-get update

Cuando salimos del contenedor, se detiene, pero aún está disponible para poder realizar un commit con los cambios realizados y crear una nueva imagen ubuntu:update:

$ docker commit $id ubuntu:update
[...]
$ docker images

En este momento, podemos parar y eliminar el contenedor, ya que podremos crear uno nuevo a partir de la imagen recién creada.

2.2 Guardar contenedores e imágenes en ficheros Tar

Usaremos los comandos save y load para crear un tarball de una imagen, o bien los comandos import y export para contenedores.

Usamos un contenedor detenido, y lo exportamos a un fichero tar:

$ docker ps -a
[...]
$ docker export $id > update.tar

Ahora, en lugar de utilizar el comando commit, usamos el comando import:

$ docker import - update < update.tar
[...]
$ docker images

Si queremos trabajar con imágenes que ya han sido creadas con el comando commit:

$ docker save -o update1.tar update
$ docker rmi update
$ docker load < update1.tar
$ docker images

2.3 Nuestro primer Dockerfile

Para poder automatizar la construcción de una imagen de Docker, tendremos que detallar los pasos en un fichero tipo ‘manifiesto’ llamado Dockerfile. Este fichero de texto usa un conjunto de instrucciones para definir la imagen base para el nuevo contenedor, que aplicaciones deben instalarse y sus dependencias, los ficheros presentes en la imagen, los puertos accesibles desde el exterior, y el comando a ejecutar en el inicio del contenedor, así como otras muchas configuraciones.

Veremos como crear nuestro primer Dockerfile:

FROM ubuntu:14.04

ENTRYPOINT ["/bin/echo"]

La directiva FROM indica la imagen base de la cual partiremos. La directiva ENTRYPOINT indica el comando que se ejecutará cuando se inicie un contenedor basado en esta nueva imagen. Para poder crear esta imagen, ejecutamos el siguiente comando:

$ docker build .
[...]
$ docker images

En este momento, disponemos de una nueva imagen para poder generar nuevos contenedores:

$ docker run [ID] Hola Docker !!
Hola Docker !!

También podemos utilizar la directiva CMD en un Dockerfile. Y presenta la ventaja de que se puede sobreescribir su contenido cuando se crea un contenedor pasando un nuevo CMD como argumento a docker run. Vamos a crear una nueva imagen con las siguientes instrucciones:

FROM ubuntu:14.04

CMD ["/bin/echo"] , "Hola Docker !"]

$ docker build .
[...]
$ docker run [ID]
Hola Docker !

Parece que es lo mismo que en el caso anterior, pero si le pasamos un nuevo argumento al comando docker run, se ejecutará en lugar de /bin/echo:

$ docker run [ID] /bin/date

Hasta ahora hemos hecho referencia a nuestras nuevas imágenes a través de su ID generado. Podemos crear una nueva imagen con el nombre curso y la etiqueta hello, a través de la opción -t.

$ docker build -t curso:hello .
$ docker images
[...]

Más información:

2.4 Migración desde Vagrant a Docker

Si hemos estado trabajando con Vagrant en nuestro entorno de desarrollo, podremos reutilizar los ficheros Vagrantfile para usarlos en Docker.

Un ejemplo de Vagrantfile que usa Docker como proveedor:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

   config.vm.provider "docker" do |d|
      d.build_dir = "."
   end

    config.vm.network "forwarded_port", guest: 5000, host: 5000
end

La opción build_dir busca un fichero Dockerfile en el mismo directorio que el Vagrantfile. Vagrant ejecuta el coamndo docker build, e inicia el contenedor:

$ vagrant up --provide=docker
[...]

Una vez que vagrant up finaliza, el contenedor se estará ejecutando y tendremos una nueva imagen. En este momento, podemos hacer uso de los comandos vagrant docker-logs y vagrant docker-run. Los comandos estándar de Vagrant, como vagrant status y vagrant destroy funcionarán con el contenedor.

Ver ejercicio en Github con ficheros de ejemplo para consultar.

2.5 Uso de Packer para crear una imagen Docker

Packer es una herramienta de HashiCorp que es capaz de crear imágenes idénticas para diferente plataformas a partir de una plantilla. Por ejemplo, desde una plantilla se pueden crear imágenes para Amazon EC2, VMWare, VirtualBox y DigitalOcean. Una de esas plataformas es Docker.

La siguiente plantilla muestra tres etapas. En la primera se indica el builder; que en este caso concreto es Docker, así como la imagen a utilizar ubuntu:14.04. La segunda define la etapa de aprovisionamiento, un shell sencillo. Y finalmente, los pasos de post-procesado.

{
"builders": [
  {
    "type": "docker",
    "image": "ubuntu:14.04",
    "commit": "true"  }],
"provisioners": [
  {
    "type": "shell",
    "script": "bootstrap.sh"
  }
],
"post-processors": [
   {
     "type": "docker-tag",
     "repository": "how2dock/packer",
     "tag": "latest"
   }
]
}

Podemos comprobar esta plantilla y ejecutarla para construír la imagen con dos comandos:

$ packer validate template.json
$ packer build template.json

Para poder probar Packer, en el siguiente archivo hay un Vagrantfile que instala Docker dentro de la máquina y descarga Packer.

Descarga: Packer_test

TODO: Ejercicio con Ansible.

2.6 Publicar imágenes en Docker Hub

Una vez que hemos generado una imagen de cierta utilidad para el resto de usuarios de Docker, tenemos a nuestra disposición Docker Hub, a donde la podemos subir y hacerla accesible, o no, para el resto de comunidad Docker.

Para poder hacer uso del servicio de Docker Hub, debemos completar las siguientes acciones:

  • Crear una cuenta en Docker Hub
  • Iniciar sesión desde nuestro host con Docker
  • Subir nuestra imagen (push)

Una vez creada nuestra cuenta en Docker Hub, accedemos a nuestro sistema con Docker, seleccionamos una imagen y la publicamos en nuestro repositorio público:

  1. Iniciamos sesión con docker login. Tendremos que introducir las credenciales.
  2. Etiquetamos una de nuestras imágenes con el nombre de usuario de Docker Hub.
  3. Hacemos push hacia el repositorio.

El primer paso (login) guarda las credenciales en ~/.docker/config.json. Una vez consultadas las imágenes disponibles, hacemos uso de una de ellas, por ejemplo:

$ docker tar packer [usuario]/packer
$ docker images

Una vez modificada la etiqueta con el formato requerido por Docker Hub, estamos preparados para realizar la subida:

$ docker push [usuario]/packer

Cuando finalice la subida, cualquier usuario puede realizar una descarga de dicha imagen con el comando docker pull [usuario]/packer.

En caso de no conocer el nombre exacto de una imagen, podemos realizar una búsqueda con el comando docker search:

$ docker search postgres

Entre los resultados podemos observar la imagen oficial (primera), y el resto serán las creadas por usuarios de Docker Hub.