IDE as a Code con Remote Containers en Visual Studio Code

Una de las cosas que más nos cuesta a los desarrolladores es empezar en un proyecto que ya está en marcha. Otras nos ocurre que los requerimientos para un nuevo proyecto nos obligan a modificar las versiones de los SDKs y las herramientas que debemos tener instaladas en local para depurar el entorno, lo cual a veces nos hace perder días hasta que todo funciona como se espera, o hace perder el tiempo al compañero/a que ha definido qué y cómo tiene que funcionar todo. Si ya pensamos en versiones que están en una fase beta/preview la historia también se puede complicar.
Por todo ello, hoy quiero contarte cómo trabajar con Remote Containers, una característica que ya lleva un tiempo siendo parte de Visual Studio Code, para que la integres como parte de tu forma de trabajar, que estoy segura que como a mi te va a encantar.

Código de ejemplo

Antes de empezar con Remote Containers, necesitas un código con el que vivir la experiencia 😃 Para este ejemplo he utilizado una aplicación en Node.js muy sencilla:

const express = require('express');
const app = express();
const port = 8080;

app.use(express.static('public'));

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
})

app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`);
})

Esta tiene la siguiente estructura:

Estructura del proyecto de Node.js

Este proyecto, como ya venías haciendo, puedes lanzarlo en local sin ningún problema, siempre y cuando tengas instalado Node.js, git, las extensiones de VS Code que te sean de utilidad para un proyecto en este lenguaje, HTML, algo de CSS, etcétera. También podrías querer usar herramientas como Nodemon, que te refrescar la aplicación de manera automática, así como otras que te hagan tu día a día más sencillo.

El lio viene cuando no sólo trabajas con esta configuración sino que haces uso de muchas otras: desarrollo con .NET Core, Python, PHP, Docker, Kubernetes, etcétera y eso te hace instalar más herramientas, SDKs y extensiones en tu Visual Studio Code que no siempre te hacen falta, y pueden terminar perjudicando al rendimiento del entorno de desarrollo. Y no solo todo esto, sino que si alguien más se suma al equipo y necesita poner su ordenador «a punto» con las herramientas, frameworks, etc. que se necesitan para echar a andar la aplicación se suelen perder varias horas (si encima trabajamos con diferentes sistemas operativos ni hablamos 😣).

Qué ofrece Remote Containers

Por todo lo anterior, y más, Visual Studio Code ofrece una característica llamada Remote Containers, o Dev Containers, que lo que te permite es que el entorno de desarrollo que tu aplicación necesita se genere dentro de un contenedor. De esta forma todos aquellos desarrolladores que abran este proyecto tendrán el mismo entorno de desarrollo de manera automática para ellos, haciendo que la experiencia de empezar en tu proyecto sea lo más ágil posible y además, si eres tú quien ha definido el entorno, puedas separar múltiples entornos de trabajo por proyecto con diferentes configuraciones en una misma máquina. ¿Mola eh?

¿Cómo empiezo?

Para poder usar esta característica tienes que tener instalado Docker en tu máquina, ya que los contenedores se ejecutarán en este. También es necesario instalar la extensión Remote Containers que es la que nos va a ayudar a levantar el entorno de desarrollo.

Extensión Remote Containers para Visual Studio Code

Si bien es cierto que esta extensión te ayuda a crear todo de forma automática, y ya viene con varias plantillas de entornos comunes, para este artículo he querido montar manualmente todo lo necesario, para que entiendas bien cómo funciona, ya que si al final te haces adicto/a a ello, como yo 🙋🏼‍♀️, querrás adaptarlo a tus necesidades.

Lo primero que necesitas es crear una carpeta llamada .devcontainer en la raíz del directorio. Esta es la que la extensión va intentar identificar para saber si tiene una configuración para montar un contenedor con las indicaciones incluidas en esta. En la misma debes tener como mínimo un archivo llamado devcontainer.json que es el que va a tener la configuración del entorno. Para este ejemplo he utilizado el siguiente:

{
    "name": "dev-demo",
    "dockerFile": "Dockerfile",
    "forwardPorts": [
        8080
    ],
    "settings": {
        "workbench.colorTheme": "Visual Studio Light"
    },    
    "containerEnv": {
        "NODE_ENV": "development",
    },
    "extensions": [
        "coenraads.bracket-pair-colorizer-2",
        "eg2.vscode-npm-script",
        "christian-kohler.npm-intellisense",
        "dbaeumer.vscode-eslint",
        "streetsidesoftware.code-spell-checker",
        "formulahendry.auto-close-tag",
        "mikestead.dotenv",
        "christian-kohler.path-intellisense",
        "davidanson.vscode-markdownlint",
        "pkief.material-icon-theme",
        "humao.rest-client"
    ],
	"postCreateCommand": "/bin/bash -c .devcontainer/post-create.sh",
    "postAttachCommand": "nodemon server.js"
}

Como ves, se trata de un JSON con diferentes apartados:

  • name: es el nombre que va a tener el dev container. Lo ideal es que este sea un nombre descriptivo.
  • dockerFile: se utiliza para indicar el nombre del Dockerfile que tiene la receta que vamos a utilizar para crear el entorno. En mi caso, en la misma carpeta .devcontainer, tengo uno como el siguiente:
FROM node:12.16.1-alpine

WORKDIR /code

RUN apk update && apk upgrade \
    && apk add git bash curl \
    && npm install -g nodemon

COPY . .

En esta estoy haciendo uso de la imagen node:12.16.1-alpine simplemente para demostrar que la configuración que tengo dentro de este entorno de desarrollo es distinta a la que tengo en local, donde estoy usando la versión 14 de Node.js. Por otro lado, instalo algunas utilidades como git, bash y cURL, además del módulo de Nodemon que usaré durante el desarrollo.

  • fordwardPorts: se trata de los puertos que mi aplicación utiliza, de tal manera que pueda acceder a ellos desde la máquina local y así que la depuración sea sencilla.
  • settings: para esta demo, y cuando tienes múltiples entornos es súper útil, he modificado el tema de Visual Studio Code para que sea evidente que no estoy en el entorno local en esa instancia del IDE. Normalmente uso la versión dark y aquí he establecido la versión light.
  • containerEnv: también he añadido una variable de entorno, que suele ser súper útil cuando desarrollas.
  • extensions: esta parte me parece súper guay, ya que aquí lo que le estoy diciendo es qué extensiones quiero que Visual Studio Code tenga instaladas para este entorno. Las cadenas que aparecen aquí es lo que se conoce como el Extension ID de la extensión, la cual puedes recuperarla fácilmente cuando estás en el apartado Extensions de Visual Studio Code y haces clic con el botón derecho sobre la misma. De hecho, si ya tienes el archivo devcontainer.json creado también es capaz de detectarlo y añadirlo por ti:
Copiar Extension ID o añadir extensiones al archivo devcontainer.json
  • postCreateCommand: he añadido esta propiedad para que veas que es posible ejecutar comandos o scripts durante el ciclo de vida del dev container. En este caso este script se lanzará cuando la creación haya finalizado y la UI esté disponible, pero tienes otros momentos también en los que ejecutar tareas. El contenido de este es el siguiente:
#!/bin/bash

# this runs in background after UI is available

#Remove node_modules folder
echo "Remove node_modules folder first"
rm -rf node_modules

#Install npm dependencies
echo "Install dependencies"
npm install
  • postAttachCommand: por último he añadido el comando nodemon server.js a esta propiedad, para que cada vez que te enganches al contenedor este por defecto levante la aplicación.

Existen muchas más opciones para añadir en el archivo devcontainer.json, por lo que todo esto es solo una muestra muy pequeña de lo que puedes hacer.

Ahora que ya sabes todo lo que hay en esta carpeta y cómo funciona la configuración definida ¿Cómo lanzamos esto? La forma más sencilla es a través de este botón de aquí:

Botón para abrir el menú de Remote Containers

Selecciona la opción Reopen in Container y el proceso comenzará a crear el contenedor y tu IDE a medida. El resultado debería de ser como el siguiente:

Visual Studio Code del entorno contenerizado

Al final el resultado en este caso será una ventana de Visual Studio Code con el tema Light, con una versión de Node.js específica, varias extensiones elegidas para desarrollar más cómodo y mi aplicación ya ejecutándose y expuesta por el puerto 8080. Y lo mejor de todo: esta configuración queda versionada en el repo del proyecto para que cualquier miembro pueda usarlo. Simplemente espectacular 🙅🏼‍♀️

Ahora que hemos visto todo esto, y hemos comprendido todos los pasos, elimina la carpeta .devcontainer, vuelve a seleccionar el icono de la parte inferior izquierda, selecciona de nuevo Reopen in Container y verás que esta vez te deja elegir entre varias configuraciones ya preconstruidas.

Configuraciones preconstruidas para Dev Containers

Como ves, no necesitas comenzar desde cero tu configuración y además ya sabes dónde tienes que tocar para terminar de darle tu toque nerd 🤓 Por último, en el apartado izquierdo, en Remote Explorer, podrás ver todos los entornos que tienes creados y cuáles están levantados:

Visual Studio Code – Remote Explorer

El código del ejemplo lo tienes en mi GitHub.

¡Saludos!