Ahora vamos a empezar a trabajar con lo que se conoce como Dockerfile, que básicamente es un fichero con la configuración que queremos para un contenedor.

Lo primero nos creamos un fichero llamado Dockerfile(sin extension)

# Indicamos una imagen base de la que partir. Usamos alpine que es una básica sin prácticamente nada
FROM alpine

#Descargamos e instalamos las dependencias que queramos. Usamos el copando apk que es un gestor de paquetes que viene dentro de la imagen de alpine
RUN apk add --update redis

# Le indicamos a la imagen que hacer o que comandos ejecutar cuando arranque
CMD ["redis-server"]

Nos situamos en la consola (o linea de comandos), en la carpeta donde tenemos el fichero y ejecutamos

docker build .

Esto nos ejecuta los comandos que tiene nuestro fichero. Para que entendamos el proceso, si miramos detenidamente el log de salida del comando veremos como en cada paso nos genera una imagen intermedia, ejecuta el comando del paso que le toque crea un snapshot para el siguiente paso y elimina la imagen anterior
Captura-de-pantalla-2018-11-07-a-las-9.29.37

Por último vemos que nos pone Successfully build ....., ese ID que nos aparece seria el nombre de nuestra imagen creada, a continuación solo tenemos que ejecutar

docker run IDimagen

Y veremos como se ejecuta perfectamente nuestro contenedor
Captura-de-pantalla-2018-11-07-a-las-9.32.26

Y si miramos que se esta ejecutando, veremos un nuevo contenedor con un nombre aleatorio que parte de nuestra imagen anteriormente creada
Captura-de-pantalla-2018-11-07-a-las-9.37.28

Usando la Caché

Si rehusamos un Dockerfile o creamo uno nuevo igual que otro o con los mismo pasos iniciales, Docker usará su caché para no tener que repetir procesos que ya ha ejecutado, veamoslo con un ejemplo. Si añadimos otra dependencia a la instalación:

FROM alpine

RUN apk add --update redis
RUN apk add --update gcc
RUN apk add --update vim

CMD ["redis-server"]

Y ejecutamos de nuevo el comando build, veremos como en los pasos iniciales nos sale Using cache, en algunas lineas, concretamente en las que se han repetido. OJO solo aparece hasta que llega a la línea que ha cambiado del Dockerfile, una vez que encuentra algo distinto ya lo hace todo de nuevo (si cambiaramos el orden de los comandos RUN lo haría todo de nuevo)

Captura-de-pantalla-2018-11-07-a-las-9.56.09

Añadir TAG a imagen

Si nos fijamos cuando se termina de construir la imagen de nuestro contenedor, nos pone un ID cualquiera, algo que es dificil de recordar. Es posible indicarle un nombre identificativo a nuestro contenedor, para ello basta con:

docker build -t aliasDockerHub/nombreImagen:latest .

Con el flag t indicamos que queremos taggear la imagen creada con un nombre, el cual es necesario que tenga la estructura que veis con el alias que tenemos en el marketplace de DockerHub

Crear Imagen a partir de un contenedor existente

La idea es configurar un contenedor como queramos y luego generar una imagen de ese contenedor para poder desplegarla. Es necesario indicarle cual el es comando de inicio

docker commit -c 'CMD["redis-server"]' idContenedor

Con este comando generaríamos una imagen que arrancaría con el comando redis-server. Este nos genera una imagen con un id gigantesco, pero para usarla no tenemos que copiar todo el id con una parte inicial que sea única y ya docker la busca.

Creando imagen propia 2.0

Hasta ahora hemos visto como crear una imagen usando otra como base e instalando algún paquete extra, pero....y ¿si queremos añadir algún desarrollo o fichero nuestro? Vamos a crear una mini aplicación en NodeJS que nos devuelva una página. Para ello vamos a crearnos una nueva carpeta para almacenar todos los ficheros de este contenedor, por ejemplo la llamaremos miniapp.
Dentro de esta carpeta lo primero nos creamos un fichero package.json que contendrá las dependencias y scripts de nuestro aplicación en NodeJS (quién no sepa de que estamos hablando puede revisarse los post de NodeJS donde lo explico todo con más detalle). Dentro de este fichero ponemos:

{
  "dependencies": {
    "express": "*"
  },
  "scripts": {
    "start": "node index.js"
  }
}

Aquí le estamos diciendo que tenemos como dependencias cualquier versión de express y un script de npm para arrancar el fichero index.js.
Ahora como os podéis imaginar tenemos que crear ese fichero, el index.js

const express = require('express');
 
const app = express();
 
app.get('/', (req, res) => {
  res.send('How are you doing');
});
 
app.listen(8080, () => {
  console.log('Listening on port 8080');
});

Ya tenemos nuestro proyecto de node preparado, ahora vamos a crear nuestro fichero Dockerfile para indicarle los pasos necesarios

#Imagen de la que partimos. En este caso usamos una imagen de node en su versión alpine (es decir con lo mínimo posible)
FROM node:alpine

#Comando nuevo. Con este comando copiamos los ficheros de un origen en nuestro equipo local a una ruta en el contenedor
COPY ./ ./
#Ejecutamos la instalación de las dependencias que hemos indicado en el package.json
RUN npm install

# Le indicamos como comando de arranque el script que hemos indicado en el package.json que básicamente inicia nuestra aplicación en Node
CMD ["npm","start"]

Ahora tenemos como comando nuevo COPY este comando copia contenido de una carpeta local (relativa a la ejecución del comando de docker build) a una carpeta en el propio contenedor.

Cambiando el directorio de ejecución en el contenedor

Con la configuración anterior, en el contenedor anterior todo se copia y se ejecuta en la raiz (/), eso en linux NO ESTÁ BIEEEEN, tenemos que indicarle un directorio en condiciones a nuestra aplicación o a nuestro ficheros. Para ello tenemos el comando WORKDIR:

WORKDIR pathDondeTrabajar

Básicamente cambia el path desde el que se está trabajando en el Dockerfile, una ruta posible para usar puede ser dentro de /usr/app (si no existe, nos crea la carpeta), por lo que quedaría así nuestro Dockerfile

#Imagen de la que partimos. En este caso usamos una imagen de node en su versión alpine (es decir con lo mínimo posible)
FROM node:alpine

# Directorio desde el que funcionarian las siguientes instrucciones
WORKDIR /usr/app

#Comando nuevo. Con este comando copiamos los ficheros de un origen en nuestro equipo local a una ruta en el contenedor
COPY ./ ./

#Ejecutamos la instalación de las dependencias que hemos indicado en el package.json
RUN npm install

# Le indicamos como comando de arranque el script que hemos indicado en el package.json que básicamente inicia nuestra aplicación en Node
CMD ["npm","start"]

Ahora si hicieramos un build del contenedor, arrancaramos una imagen y nos conectaramos con exec -it podríamos ver que nos conectamos directamente al directorio que hemos indicado como working directory....peeeeeero realmente todavía no funcionaria nuestra aplicación, veamos lo último para hacerla funcionar.

Mapear puertos locales a puertos de contenedor

El que tenga algunos conocimientos de node se habrá dado cuenta de que lo que hacemos es arrancar una aplicación en el puerto 8080, pero claro, es el puerto 8080 de nuestro contenedor no de nuestro equipo, tenemos que pensar que un contenedor es prácticamente como un equipo independiente, como si hablaramos de una máquina virtual.

Para poder hacer uso de un puerto del contenedor, tenemos que mapear un puerto local a uno del contenedor, no tienen porque ser iguales, pero si tiene que estar libre nuestro puerto local (hace algo similar a una redirección).
Este mapeo se hace a la hora de crear el contenedor no al construir la imagen, es decir, el mapeo de puertos no se hace en nuestra fase de docker build, si no en la fase de docker create/docker run de la siguiente manera:

docker run -p ptoLocal:ptoContenedor IDimagen

Captura-de-pantalla-2018-11-08-a-las-9.47.32

Si ahora hacemos un docker ps, veremos que tenemos algo en la columna PORTS

Captura-de-pantalla-2018-11-08-a-las-9.48.20

Con esto ya tendríamos nuestra app funcionando perfectamente, solo tenemos que entrar a localhost:5000 en nuestro navegador y voilá.