Docker como entorno de desarrollo local
¿Ves alguna errata o quieres modificar algo? Haz una Pull Request
En este artículo quiero compartir como implementar un entorno de desarrollo local en tu ordenador para tus aplicaciones empleando para ello Docker. Lo configuraremos de manera que funcione como un LiveReload, a cada cambio que hagamos en el código de nuestra aplicación, el contenedor lo refleje.
En otros artículos he comentado cómo crear contenedores de Docker en Mac OSX e incluso cómo desplegarlos en producción en Digital Ocean.
Hoy quiero hablar de Docker como una alternativa a Vagrant como entorno de desarrollo y provisionamiento.
Docker ha evolucionado mucho en poco tiempo. En un principio solo era posible utilizarlo en un sistema Linux, debido a que utiliza la tecnología de contenedores del sistema operativo. Hace poco empezó a ser utilizable en sistemas Mac y Windows gracias la máquina virtual boot2docker
. Y hoy en día gracias a Docker Toolbox es posible utilizarlo en cualquier sistema.
Definiendo el entorno de desarrollo con Docker Compose
En el ejemplo de este artículo voy a utilizar Ubuntu 14.04 como sistema operativo base y una aplicación Node.js muy sencilla que ilustre lo que queremos hacer.
Primero de todo vamos a crear una carpeta para nuestro proyecto, en el directorio que prefieras de tu máquina local. En el vamos a incluir un fichero Dockerfile
, un fichero docker-compose.yml
y una carpeta para el código fuente de nuestra aplicación que puede ser de nombre app/
.
Dentro de app/
vamos a crear un fichero package.json
muy simple con el siguiente código:
{
"name": "project",
"scripts": {
"start": "nodemon app.js"
},
"dependencies": {
"express": "^4.x.x"
},
"devDependencies": {
"nodemon": "*"
}
}
Y un fichero con el código de la aplicación de nombre app.js
con el siguiente contenido (de momento):
console.log("Hola Mundo!");
Ahora procedemos con el fichero de la imagen Docker, el Dockerfile
, que tendrá el siguiene contenido:
FROM node:4.4.0 MAINTAINER cazaustre@gmail.com WORKDIR /app COPY app/ .
En éste fichero básicamente estamos diciendo que utilice la imagen de Node versión 4.4.0 como base, y que se coloque en el directorio /app
para trabajar allí. Lo siguiente será que copie el contenido de la carpeta app
local en el directorio actual del contenedor (que sera app
por la instrucción anterior)
Dejamos este fichero así y continuamos con el fichero docker-compose.yml
:
web: build: . command: 'npm start' volumes: - ./app:/app
Este fichero nos ayuda a reducir código del Dockerfile y de comandos a la hora de correr el contenedor. Más adelante también nos servirá para defiir otros contenedores y enlazarlos.
Creamos un contenedor de nombre web
que realiza lo siguiente:
-
Con
build
indicamos que imagen de Dockerfile tiene que utilizar. Con la notación.
leerá el fichero Dockerfile de directorio principal (donde se encuentre el ficherodocker-compose.yml
) -
Con
command
indicamos que comando tiene que ejecutarse al correr el contenedor. En este casonpm start
que ejecuta el scriptstart
definido en elpackage.json
(nodemon app.js
) -
Y
volumes
es la parte más importante, ya que nos permite enlazar un directorio local con un directorio dentro del contenedor. En este caso, el directorioapp
donde está definido el código de la aplicación, lo enlazamos conapp
dentro del contenedor. Este es el directorio de trabajo que ya definimos conWORKDIR
en el ficheroDockerfile
.
Para poner en marcha este workflow, primero ejecutamos $ docker-compose build
para construir los contenedores:
root@ubuntu:/home/carlosazaustre/Development/project# docker-compose build
Building web Step 1 : FROM node:4.4.0 ---> 564c70a84ab7 Step 2 : MAINTAINER
cazaustre@gmail.com ---> Using cache ---> 62f115911e67 Step 3 : WORKDIR /app
---> Using cache ---> eb7e7144e107 Step 4 : COPY app/ . ---> b151f82fbff3
Removing intermediate container 9afda7bedfff Successfully built b151f82fbff3
Y después $ docker-compose up
para ponerlo en marcha:
root@ubuntu:/home/carlosazaustre/Development/project# docker-compose up
Recreating project_web_1 Attaching to project_web_1 web_1 | npm info it
worked if it ends with ok web_1 | npm info using npm@2.14.20 web_1 | npm
info using node@v4.4.0 web_1 | npm info prestart project@ web_1 | npm info
start project@ web_1 | web_1 | > project@ start /app web_1 | > nodemon
app.js web_1 | web_1 | [nodemon] 1.9.1 web_1 | [nodemon] to restart at any
time, enter rs
web_1 | [nodemon] watching: . web_1 | [nodemon] starting
node app.js
web_1 | Hola Mundo! web_1 | [nodemon] clean exit - waiting for
changes before restart
Si todo sale bien, al final en el terminal tiene que aparecer el mensaje Hola Mundo!
que es lo que hace el fichero app.js
Creando nuestra App Node.js
Ahora vamos a modificar un poco el fichero app.js
para que cree un pequeño servidor web con Express
:
const express = require("express");
const app = express();
const port = 3000;
app.set("port", port);
app.get("/", (req, res) => {
res.send("Hola Mundo!");
});
app.listen(app.get("port"), (err) => {
console.log(`Server running on port ${app.get("port")}`);
});
Y cambiamos el código del fichero docker-compose.yml
para definir un par de cosas nuevas:
web: build: . command: sh -c 'npm install; npm start' ports: - '3000:3000'
volumes: - ./app:/app
-
El comando que ejecutaremos ahora es
sh -c 'npm install; npm start'
que lo que hace es abrir un terminal de shell en el contenedor y ejecutar los comandosnpm install
para instalar las dependencias definidas en elpackage.json
ynpm start
para correr el scriptstart
. -
Con
ports
exponemos fuera del contenedor el puerto que está utilizando la aplicación, en este caso el3000
Si ahora ejecutamos de nuevo $ docker-compose build
y $ docker-compose up
tendremos la siguiente salida
Recreating project_web_1 Attaching to project_web_1 web_1 | npm info it
worked if it ends with ok web_1 | npm info using npm@2.14.20 web_1 | npm
info using node@v4.4.0 web_1 | npm info prestart project@ web_1 | npm info
start project@ web_1 | web_1 | > project@ start /app web_1 | > nodemon
app.js web_1 | web_1 | [nodemon] 1.9.1 web_1 | [nodemon] to restart at any
time, enter rs
web_1 | [nodemon] watching: . web_1 | [nodemon] starting
node app.js
web_1 | Server running on port 3000
Como se puede ver, el servidor está escuchando en el puerto 3000
. Si abrimos un navegador en la dirección http://localhost:3000
veremos lo siguiente.
La IP no será
localhost
si estamos en Mac o Windows, debido a que Docker necesita una máquina virtual linux para ejecutarse. En ese caso será la IP que nos proporcione la máquina virtual.
Para este ejemplo estoy usando Linux Ubuntu, por eso empleo
localhost
Realizando cambios en nuestra App
Como tenemos el contenedor corriendo, el volumen enlazado, y el script nodemon
ejecutándose, si hacemos cambios en el código, se verán reflejados en el contenedor.
Vamos a cambiar el código de app.js
para que a la ruta /:nombre
nos muestre el nombre que pasamos en la URL:
const express = require("express");
const app = express();
const port = 3000;
app.set("port", port);
app.get("/:nombre", (req, res) => {
res.send(`Hola ${req.params.nombre}!`);
});
app.listen(app.get("port"), (err) => {
console.log(`Server running on port ${app.get("port")}`);
});
En cuanto salvemos, la terminal mostrará el siguiente mensaje:
... web_1 | [nodemon] restarting due to changes... web_1 | [nodemon]
starting node app.js
web_1 | Server running on port 3000
Y si en el navegador cambiamos la URL a http://localhost:3000/Carlos
tendremos lo siguiente:
De esta manera, tenemos un entorno de desarrollo definido gracias a Dockerfile, que luego podremos trasladar a producción, pero a su vez lo podemos utilizar al mismo tiempo que vamos cambiando nuestro código como si el entorno estuviese instalado en nuestra máquina virtual.