Real Time Apps con Hotwire

Durante Diciembre DHH (creador Ruby on Rails, founder de Basecamp) estuvo twitteando mucho sobre algo que estaban desarrollando para su nuevo proyecto HEY y que iban a hacer release a la comunidad. Efectivamente el 22 de Diciembre lo liberaron y dieron a conocer el nombre Hotwire.

https://twitter.com/dhh/status/1341420143239450624

Como siempre en el mundo de la tecnología, todo vuelve y poder hacer server side rendering y mandar HTML “behind the scenes” parece ser lo nuevo.

En esta “nueva movida”, estoy muy de acuerdo con el amigo DHH, mantener todo un backend con toda una lógica, y tener que replicar ciertas cosas en un frontend de React/Angular/Vue o cualquier cosa que se les ocurra, es en muchísimo trabajo. Si bien creo que en ciertos casos está bueno y vale la pena el esfuerzo, muchas de las aplicaciones de hoy en día se podrían simplificar usando este “nuevo” approach.

Ahora si, sin más preámbulos veamos un poco de que se trata

¿Cómo funciona Hotwire?

Pueden leer mucho en la web, y me tomo el atrevimiento de hacer una traducción con mis palabras

Hotwire es un approach alternativo para construir aplicaciones web sin usar mucho Javascript, se envia HTML, en lugar de JSON a través del cable. Esto hace que las páginas carguen más rápido, mantienen el renderizado de los templates del lado del servidor y permite una experiencia de desarrollo más simple y productiva, sin sacrificar velocidad y experiencia asociada a una Single Page App.

Hotwire utiliza 3 cosas Turbo, Stimulus y Strada (al momento de escribir este post Strada aun no esta disponible).

El 80% de las interacciones se hace con Turbo, por lo tanto es el corazón de Hotwire. Si ya tenían experiencia con TurboLinks, es el mismo proyecto que cambió de nombre y está ahora generalizado para formularios. Turbo se usa para hacer stream de actualizaciones parciales de la página sobre WebSockets. Todo esto sin escribir nada de Javascript.

Cuando necesitamos hacer algo personalizado, ahi podemos usar Stimulus. Este es un framework de Javascript para el HTML que ya tenés, por lo que poniendo un poco de código en el HTML que ya tenés, se crean componentes Javascript. Esto no lo vamos a usar en esta prueba que voy a mostrar luego. Probablemente escriba sobre esto más adelante.

Por último Strada, parece que va a estar más relacionado con aplicaciones para dispositivos móviles.

Pueden leer más a fondo cómo funciona en el Handbook.

Manos a la obra

El ambiente de desarrollo

Vamos a usar lo mismo que en el post de Rails + Postgres, pero a nuestro docker-compose le vamos a agregar Redis, que lo necesitamos para configurar Action Cable.

version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
VARIANT: "2.7"
NODE_VERSION: "lts/*"
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
db:
image: postgres:latest
restart: unless-stopped
volumes:
postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
redis:
image: redis:latest
volumes:
postgres-data:
view raw docker-compose.yml hosted with ❤ by GitHub

Además de modificar el archivo database.yml, para conectar a Postgres, tenemos que cambiar cable.yml para que user redis (que es el nombre del servicio en el docker-compose) como url.

development:
adapter: redis
url: redis://redis:6379/1
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379/1" } %>
channel_prefix: BeerStyles_production
view raw cable.yml hosted with ❤ by GitHub

El Proyecto

En muchos de los posts que estuve leyendo, para mostrar como funciona Hotwire, se usa un proyecto de un clone de Twitter.

En lugar de eso, a mi me gustaría hacer algo super sencillo que es una aplicación para crear estilos de cervezas.

Para arrancar creamos el proyecto, vamos a usar Postgres.

rails new BeerStyles -d postgresql

Ahora agregamos a nuestro gem file hotwire-rails

bundle add hotwire-rails

Una vez que tenemos esto vamos a configurar Hotwire, para esto tenemos una rake task que nos configura todo y nos pone el boilerplate para usarlo.

rails hotwire:install

Una vez que tenemos esto vamos a crear el scaffold para el estilo.

rails g scaffold Style name:string description:string

Ahora chequeamos que tengamos la configuración del punto anterior en cable.yml y database.yml.

No se olviden de crear la base de datos y correr las migraciones

rails db:create db:migrate

Por último probamos que nuestro proyecto funcione

rails s

Entramos entonces a http://localhost:3100/styles.

Usando Hotwire

Refactor previo

Vamos a modificar un poco lo que nos generó Rails para poder adaptarlo mejor a Hotwire.

Este es el código que nos generó Rails para app/view/styles/index.html.erb

<p id="notice"><%= notice %></p>
<h1>Styles</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @styles.each do |style| %>
<tr>
<td><%= style.name %></td>
<td><%= style.description %></td>
<td><%= link_to 'Show', style %></td>
<td><%= link_to 'Edit', edit_style_path(style) %></td>
<td><%= link_to 'Destroy', style, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Style', new_style_path %>
view raw index.html.rb hosted with ❤ by GitHub

Vamos a hacer un pequeño refactor. Dejamos de usar una tabla y migramos todo el código que muestra cada estilo a su propio template.

Creamos app/view/styles/_style.html.rb

<div class="card m-4">
<div class="card-title">
<%= style.name %>
</div>
<div class="card-body">
<%= style.description %>
</div>
<div class="card-footer bg-transparent border-success">
<%= link_to 'Edit', edit_style_path(style) %>
<%= link_to 'Destroy', style, method: :delete, data: { confirm: 'Are you sure?' } %>
</div>
</div>
view raw _style.html.erb hosted with ❤ by GitHub

Actualizamos app/view/styles/index.html.erb

<p id="notice"><%= notice %></p>
<h1>Styles</h1>
<%= render @styles %>
<%= link_to 'New Style', new_style_path %>
view raw index.html.erb hosted with ❤ by GitHub

Turbo Streams

Para que funcione Hotwire, y exista una comunicación con todos los clientes que están viendo la aplicación, vamos a usar Turbo Streams.

Lo que hacemos es colgarnos de los eventos del modelo create, update y destroy. Cuando algo de eso pasa en el modelo, tomamos alguna acción en el stream.

Vamos a modificar el modelo de Styles app/models/style.rb

class Style < ApplicationRecord
after_create_commit {broadcast_prepend_to "styles"}
after_update_commit {broadcast_replace_to "styles"}
after_destroy_commit {broadcast_remove_to "styles"}
end
view raw style.rb hosted with ❤ by GitHub

Ahora cada vez que pase algo en el modelo, Rails va a enviar un mensaje al canal styles. Por ejemplo, si se crea un nuevo estilo, se va a agregar `brodcast_prepend_to`.

Ahora tenemos que suscribirnos a ese canal, entonces vamos a modificar, app/view/styles/index.html.erb.

En este caso vamos a agregar un turbo_stream_from con el que vamos a establecer la conexión entre el WebSocket y el stream de styles. Después envolvemos la lista de estilos con turbo_frame_tag, esto nos va a permitir parchear el DOM, con lo que se publique en el stream de styles desde el backend

<p id="notice"><%= notice %></p>
<h1>Styles</h1>
<%= turbo_stream_from "styles" %>
<%= turbo_frame_tag "styles" do %>
<%= render @styles %>
<%end%>
<%= link_to 'New Style', new_style_path %>
view raw index.html.rb hosted with ❤ by GitHub

También vamos a actualizar app/view/styles/_style.html.erb envolviendo todo dentro de un nuevo turbo_frame_tag, pero esta vez agregando además el id del estilo.

Es importante usar la función dom_id(style), porque si ponemos sólo style nos va a poner como ID la dirección de memoria de nuestro estilo. Este cambio lo hacemos para poder referenciar cada estilo en el DOM y poder modificarlos o borrarlos.

<%= turbo_frame_tag dom_id(style) do %>
<div class="card m-4">
<div class="card-title">
<%= style.name %>
</div>
<div class="card-body">
<%= style.description %>
</div>
<div class="card-footer bg-transparent border-success">
<%= link_to 'Edit', edit_style_path(style) %>
<%= link_to 'Destroy', style, method: :delete, data: { confirm: 'Are you sure?' } %>
</div>
</div>
<% end %>
view raw _style.html.rb hosted with ❤ by GitHub

Ahora lo que podemos hacer es probarlo, abrimos una consola de Rails en la terminal de VS Code.

rails c

Y desde ahi podemos crear, borrar y editar estilos y ver como sin refrescar el browser se actualizan.

Edicion inline

Para hacer esto, vamos a hacer un pequeño refactor de app/views/styles/edit.html.rb.

Envolvemos nuevamente en un turbo_frame_tag el form, borramos los links que teníamos y creamos un nuevo link de cancel.

<h1>Editing Style</h1>
<%= turbo_frame_tag dom_id(@style) do %>
<%= render 'form', style: @style %>
<%= link_to 'Cancel', styles_url %>
<% end %>
view raw edit.html.rb hosted with ❤ by GitHub

También tenemos que cambiar en el app/controllers/styles_controller.rb, el lugar a donde redirige cuando hacemos una actualización de un estilo. Buscamos el método update y modificamos la redirección en caso de que el update fue exitoso. También actualizamos la rama del if en el caso de que no sea existo, agregando una línea para que nos pueda dar el estilo que se quiso crear y podamos mostrar las validaciones.

def update
respond_to do |format|
if @style.update(style_params)
format.html { redirect_to styles_url, notice: "Style was successfully updated." } # Cambió el redirect_to
format.json { render :show, status: :ok, location: @style }
else
#Agrego esta linea
format.turbo_stream { render turbo_stream: turbo_stream.replace(@style, partial: "styles/form", locals: { style: @style}) }
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @style.errors, status: :unprocessable_entity }
end
end
end
view raw styles_controller.rb hosted with ❤ by GitHub

Ahora cuando editamos un estilo se va a desplegar el formulario de actualización en la misma lista de estilos.

Crear nuevos estilos

Para poder crear estilos, vamos a agregar el formulario de creación en el archivo de app/views/styles/index.html.erb (linea 5)

<p id="notice"><%= notice %></p>
<h1>Styles</h1>
<%= turbo_stream_from "styles" %>
<%= render "styles/form", style: @style %>
<%= turbo_frame_tag "styles" do %>
<%= render @styles %>
<%end%>
<%= link_to 'New Style', new_style_path %>
view raw index.html.rb hosted with ❤ by GitHub

Tenemos que hacer algunos cambios en el controller. Hay que crear una variable @style en el método index y también hacer los mismos cambios que hicimos en el método update, ahora en el método create.

def index
@styles = Style.all
@style = Style.new
end
def create
@style = Style.new(style_params)
respond_to do |format|
if @style.save
format.html { redirect_to styles_url, notice: "Style was successfully created." }
format.json { render :show, status: :created, location: @style }
else
format.turbo_stream { render turbo_stream: turbo_stream.replace(@style, partial: "styles/form", locals: { style: @style}) }
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @style.errors, status: :unprocessable_entity }
end
end
end
view raw styles_controller.rb hosted with ❤ by GitHub

Ahora si tenemos un CRUD completo, utilizando Hotwire, de esta forma podemos hacer sitios mucho más dinámicos sin necesidad de tener un frontend hecho en Angular, React o agregando toneladas de javascript.

Si quieren ver el código del proyecto se los dejo acá:

ignaciojonas/hotwire-test

Application to Test Hotwire This app is a default Rails example to CRUD beer styles using Hotwire. There is a step by step guide in my blog (Spanish). In order to make this app easily run, you can use VSCode and Remote Containers. After reopen the folder in the container, just excute in the terminal.

Espero que puedan empezar a usar Hotwire en sus proyectos.

Nos leemos!

Ambientes de Desarrollo Docker (Rails + Postgres)

Vamos a darle vuelta más de tuerca a nuestro ambiente de desarrollo de Ruby on Rails.

Algo más que común, es el uso de una base de datos en una aplicación web. Es algo que en la mayoría de los proyectos vamos a usar.

Como vimos en el post anterior, armamos un ambiente de desarrollo, usando la extension Remote Containers de Visual Studio Code. Pero solo teníamos el container que tiene la aplicación y usábamos SQLite como base de datos.

En este post vamos a ir un paso más y crear un ambiente con múltiples containers. La aplicación y la base de datos. Esto mismo se puede usar en caso que necesitemos Redis, o cualquier otro servicio corriendo en nuestra aplicación.

Al momento de escribir este post hay un pull request en el proyecto de los templates de Remote Containers con una configuración parecida a la que te voy a mostrar.

Probablemente cuando esto se integre al proyecto va a ser más fácil elegir este template en la lista como elegimos el de Ruby on Rails.

Manos a la obra

Para arrancar vamos a necesitar todo lo mismo que les conté en el post anterior.

Ahora, en lugar de elegir el template Ruby on Rails, vamos a elegir Docker from Docker Compose.

Esto nos va a agregar varios archivos, que vamos a cambiar casi todos y borrar algunos.

La carpeta library-scripts la borramos y el resto de los archivos los vamos a modificar.

devcontainer.json

En este archivo lo que cambio es:

  • name: Ponerle un nombre más representativo en este caso Ruby on Rails + Postgres
  • extensions: Poner rebornix.Ruby
  • fowardPorts: Agregar el 3000 que es donde corre Rails.
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/docker-from-docker-compose
{
"name": "Ruby on Rails + Postgres",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"rebornix.Ruby"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker –version",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}
view raw devcontainer.json hosted with ❤ by GitHub

Dockerfile

Lo modifico totalmente, uso en su lugar el que se creó cuando armamos el ambiente de Ruby on Rails sin base de datos.

# [Choice] Ruby version: 2, 2.7, 2.6, 2.5
ARG VARIANT=2
FROM mcr.microsoft.com/vscode/devcontainers/ruby:0-${VARIANT}
# Install Rails
RUN gem install rails webdrivers
ARG NODE_VERSION="lts/*"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install –no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install additional gems.
# RUN gem install <your-gem-names-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
view raw Dockerfile hosted with ❤ by GitHub

docker-compose.yml

En este archivo es donde pasa la magia :).

Vamos a agregar 2 servicios:

  • App: Este es el mismo container que usábamos antes, que está definido en el Dockerfile, acá vamos a correr nuestra aplicación.
  • Db: Este es el container de la base de datos.

Nota: Como te decía más arriba aca podrias poner los servicios que quisieras, Redis, adminer, etc.

version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
VARIANT: "2.7"
NODE_VERSION: "lts/*"
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
db:
image: postgres:latest
restart: unless-stopped
volumes:
postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
volumes:
postgres-data:
view raw docker-compose.yml hosted with ❤ by GitHub

Turn on the engines

Ahora estamos listos para arrancar los motores, nuevamente buscamos en la opciones de Remote Container, Reopen in Container.

Ahora vamos a crear una aplicación de rails, pero usando postgres

rails new myapp --database=postgresql

En el archivo de configuración de la base de datos (config/database.yml), agregamos el host, user y password a la sección default.

default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: db
user: postgres
password: postgres
view raw database.yml hosted with ❤ by GitHub

Nota: Dentro de Docker para comunicarnos con otros servicios, la URL que usamos es el nombre del servicio. En este caso el host que vamos a usar en nuestra aplicación va a ser db.

Probemos la DB

Para poder probar la base de datos, vamos a crear algo en Rails para usarla. Para eso usamos el generator de Rails, para crear un CRUD, en este caso de Clientes.

rails g scaffold Customer name:string address:string

Ahora si , tenemos migraciones, entonces ejecutamos

rails db:create db:migrate

Ahora si podemos entrar a http://localhost:3000/customers y probar nuestra app, creando, editando y borrando clientes.

Espero que les sirva y lo puedan usar.

Nos leemos!

Ambientes de desarrollo en Docker + VSCode

Hace unos meses que estuve probando y usando un poco una extensión de Visual Studio Code que se llama Remote Containers. La verdad que me pareció super simple, cómodo y muy potente.

Remote Containers te ayuda a tener un ambiente de desarrollo para muchísimas tecnologías en 5 minutos.

Para este ejemplo voy a usar una aplicación de Ruby on Rails default.

¿Qué necesitamos?

Para seguir este mini tutorial necesitas tener instalado:

Setup

Para empezar abrimos VSCode, en una carpeta nueva y vamos a la parte de Extensions, ahí tenemos que instalar la extensión Remote Containers.

Una vez instalada, vamos a poder usarla:

  • Haciendo click en el icono de abajo a la izquierda.
  • Abriendo la consola de comandos (Command Palette) en el menú View o con el comando CMD + Shift + p (en Mac).

Crear el container

Sabemos que vamos a estar trabajando en un proyecto de Ruby on Rails, entonces abrimos la consola de comandos y buscamos: Remote-Container: Add development container…

Una vez dentro de ese menu, buscamos Ruby on Rails y luego la versión de Ruby que queramos usar, al momento de este post, la última disponible es 2.7, aunque ya salió Ruby 3.0.

Esto nos va a crear una carpeta con 2 archivos:

  • devcontainer.json: Donde esta toda la configuración del ambiente.
  • Dockerfile: Es la imagen de Docker que vamos a usar.

Ambos archivos estan muy bien documentados y tienen secciones que se pueden descomentar para poder hacer que se instalen gemas adicionales, paquetes de Node, etc.

En principio, lo único que vamos a hacer, es descomentar en devcontainer.json la parte de forwardPorts. Así vamos a poder conectarnos desde el browser (en nuestra máquina) a la aplicación que va a estar corriendo en Docker. Entonces configuramos el puerto 3000 que es el default en las apps de Rails.

// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/ruby-rails
{
"name": "Ruby on Rails",
"build": {
"dockerfile": "Dockerfile",
"args": {
// Update 'VARIANT' to pick a Ruby version: 2, 2.7, 2.6, 2.5
"VARIANT": "2.7",
"NODE_VERSION": "lts/*"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"rebornix.Ruby"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "ruby –version",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}
view raw devcontainer.json hosted with ❤ by GitHub

Probando el ambiente

Ahora vamos a probar el ambiente, para eso, usando la consola de comandos buscamos Remote-Containers: Reopen in Container

Esto nos va a re-abrir Visual Studio Code y nos va a indicar, donde esta el icono de Remote Containers, si estamos viendo el código dentro de un container.

Ahora si, abrimos la terminal (Ctrl + ` o desde el menu Terminal) desde donde vamos a poder ejecutar comandos dentro del container. Por ejemplo podemos ver que versión de Ruby y de Rails tenemos instalado.

Ahora podemos ejecutar, en esa consola, el comando de Rails para crear una app.

rails new MyApp

O si, como yo, preferís que el proyecto quede al nivel de la carpeta que creaste, anda un nivel para arriba y ejecuta.

rails new [nombre-de-la-carpeta]

Una vez que terminó, entramos a la carpeta del proyecto y ejecutamos rails s para poner a andar el web server.

Entramos en nuestro browser a http://localhost:3000 y voilà, nuestra app de Rails está corriendo dentro del container y podemos usarla desde nuestro browser.

El próximo paso será poder configurar el ambiente para que no use SQLite, sino Postgres o alguna otra base de datos. Pero eso es para otro post.

Espero que lo puedan usar en sus proyectos.

Nos estamos leyendo.

Docker – Primeros Pasos

docker

En estos días estuve investigando un poco más como funciona Docker, tratando de entender la arquitectura y aprendiendo a usar los comandos de Docker.

Docker vs Maquinas Virtuales

El concepto de maquinas virtuales para desarrollar aplicaciones es bastante conocido. Desarrollamos una aplicación PHP en nuestra maquina (digamos que usamos Windows), pero nuestro server, el que va a hostear la aplicación, corre en Linux. Entonces creamos una maquina virtual en nuestra maquina para probar como funciona en ese sistema operativo.

Reproducir estos ambientes es pesado, ya que deberíamos armar una maquina virtual, exportarla y compartirla con el equipo de desarrollo. Imagínense el problema si somos un equipo de desarrollo en diferentes lugares del mundo y necesitamos compartirnos una VM que pesa 20 Gb, aún más si necesitamos hacer cambios a esos ambientes para replicar los cambios hechos en el ambiente de producción.

Algo un poco mas avanzado que podemos hacer es usar Vagrant para crear maquinas virtuales y Ansible, para provisionar maquinas virtuales instalando lo que necesitamos (dependencias y nuestra aplicación). De esta forma con unos pocos archivos en nuestro repositorio tenemos la configuración de nuestro ambiente reproducible por todo el equipo. El problema de mantener la misma configuración que el ambiente de producción sigue, pero un poco mas sencillo, es cuestión de modificar nuestros scripts de Ansible y todo debería seguir funcionando.

vagrant (1)Podemos ver en el gráfico como desde nuestros scripts de Ansible podemos tanto provisionar nuestra maquina virtual, como así también los ambientes de producción o staging.

El concepto de Docker va un poco mas allá. Que pasa si pudiésemos con un solo archivo crear una imagen que sea la misma que corra en nuestra VM de desarrollo, en staging, en producción, en la maquina de un tester, etc.

Acá entran en juego un poco de terminología de Docker:

Imagen: Es el conjunto de binarios, bibliotecas y mi aplicación. Todo esto se arma en un paquete que va a correr en un container.

Container: Es el lugar donde va a correr la aplicación con todas sus dependencias. Es un lugar aislado, nada de afuera lo modifica. Es decir, si corremos un container en un Linux Debian en producción, pero en nuestra máquina tenemos otra distribución va a funcionar igual, ya que todas las dependencias están dentro del container.

Dockerfile: Es el archivo que tiene la receta de como crear la imagen.

Docker Hub: Es el lugar donde se guardan las imágenes de Docker

docker vs vagrantTenemos un Dockerfile en nuestro proyecto, a partir de ese archivo creamos una imagen que publicamos en Docker Hub. Esta imagen tiene nuestra aplicación + dependencias.

Queremos instalar nuestra aplicación en producción, corremos un container con la imagen que esta en Docker Hub, lo mismo si queremos instalarla en staging o en un cluster.

Comandos Basicos de Docker

Los comandos que más vamos a utilizar en Docker son:

docker run <imagen>

Este comando nos permite correr un container con una imagen que tengamos localmente o que exista en Docker Hub.

docker start <nombre|id>

Nos permite arrancar un container que ya tengamos en nuestra maquina que este apagado, ya sea usando el nombre que le asignamos al crearlo, o el id autogenerado.

docker stop <nombre|id>

Nos permite parar un container que tengamos corriendo en nuestra maquina, nuevamente por nombre o id.

docker ps [-a incluye los containers apagados]

Este comando nos lista los containers que tengamos ejecutando en nuestra maquina, si agregamos la opción -a, nos muestra todos los containers que están en el registro, que alguna vez corrimos y nunca borramos.

Tengan cuidado porque cada vez que ejecutan docker run se genera un nuevo container y creanme que pueden llegar a tener muchísimos.

docker rm [nombre|id]

Este comando nos permite borrar los containers creados en nuestra maquina, tanto por nombre, como por id.

Manos a la obra

Vamos a empezar a  usar estos comandos y para esto vamos a usar una imagen que esta en Docker Hub que se llama tutum/hello-world .

Para ejecutar el container en nuestra maquina vamos a la consola de Docker y ejecutamos:

docker run -d tutum/hello-world

La opción -d nos crea pone a correr el container en background, si no ponemos esa opción nuestra consola va a quedar freezada y vamos a tener que abrir otra para interactuar con Docker.

Luego ejecutamos

docker ps

dockerps

Ya tenemos nuestro primer container corriendo :). Fíjense que dice Ports 80/tcp, entonces el container esta escuchando en ese puerto.

Esta imagen tiene una página donde nos muestra el nombre de nuestro container, intentemos entonces accederla.

Desde nuestro navegador ponemos en la URL a la IP de la maquina de Docker, en mi caso http://10.0.0.100.

Lo que nos va a pasar es que no vemos nada, el navegador nos dice ‘Connection Refused’.

Lo que nos pasa es que si bien el container esta escuchando al puerto 80, no tenemos un mapeo hecho de ese puerto del container, con el de la máquina de Docker. Para esto tenemos que agregar algunos parametros al comando run.

docker run -p 8080:80 tutum/hello-world

La opción -p nos permite mapear un puerto de la máquina de Docker, al container. En este caso, el puerto 8080 de la máquina de Docker se mapea al 80 del container.

Ahora si accedemos a la IP de la maquina de Docker, en mi caso http://10.0.0.100:8080 y vemos algo así:

Tutum-hello-world

Si ahora ejecutamos nuevamente docker ps vamos a ver lo siguiente

Dockerps

Vemos entonces que en la columna PORTS nuestro puerto esta mapeado.

Ahora vamos a parar el container.

docker stop 0bd2b89a3b5a

Cuando ejecutamos este comando el container se para, si queremos acceder en el navegador no vamos a ver nada y si ejecutamos nuevamente el comando docker ps, no vamos a ver nada.

La unica forma que podamos ver los contenedores parados es con el comando;

docker ps -a

dockerpsa

Ahora si quisieramos podriamos arrancar uno de estos containers apagados con el comando

docker start 0bd2b89a3b5a

Este comando pone en funcionamiento el container que habíamos apagado antes. Para ver si esta funcionando ejecutamos

docker ps

Vemos que todas los comandos los ejecutamos usando el id del container, sería mas cómodo usar el nombre. Para poder ponerle un nombre al container utilizamos la opción –name

docker run --name hello-world -p 8080:80 tutum/hello-world

Lo último que nos queda hacer es eliminar un container, para eso ejecutamos.

docker rm hello-world

Tengan en cuenta que para borrar un container antes tiene que estar parado.

Espero les sirva, nos leemos!

Qué es Docker?

dockerDocker es una plataforma para desarrollar, shippear y ejecutar aplicaciones utilizando la tecnología de virtualización de contenedores.

La idea es sencilla pero muy poderosa, todos sabemos que hace algunos años, para tener una aplicación corriendo en internet, necesitábamos un server exclusivo para esta. Es decir, así tuviéramos corriendo un servicio que atendia millones de requests por segundo o uno request por hora, teníamos un server para eso. Las contras de esto son conocidas, desperdicio masivo de recursos.

Un camino mas inteligente fue el de las maquinas virtuales, super utilizado actualmente, tenemos un solo server, que contiene varias maquinas virtuales corriendo en el. Es decir, que en lugar de tener una sola aplicación por server, ahora tenemos N aplicaciones en un server.  Sin embargo seguimos teniendo una desventaja, tenemos nuestro server con un sistema operativo + hypervisor. Y cada una de nuestras VMs tienen un sistema operativo y sobre este nuestra aplicación.

virtualizacion

Ahora si, que pasaría si en lugar de tener que instalar un sistema operativo en cada VM, podemos tener un contenedor que tenga nuestra aplicación y dependencias y que se comunique directamente con el kernel del sistema operativo del server.

Docker

Esta es la arquitectura de un server con Docker, tenemos el server con un sistema operativo, sobre este corre el Motor de Docker (Docker Engine) y sobre este cada uno de los container con la aplicación y sus dependencias.

Ventajas del uso de Docker

Ahorro de Recursos

Nuestros contenedores solo tienen nuestra aplicación y sus dependencias, no tenemos más un sistema operativo que consuma recursos.

Consistencia entre ambientes

Nuestro ambiente de desarrollo va a ser el mismo que en producción, no mas Infierno de Dependencias o En mi maquina funciona!. Si nuestra aplicación funciona dentro de un container de Docker, y en producción utilizamos Docker, estamos seguros de que la aplicación va a funcionar.

Compartir el ambiente de desarrollo

Ya no vamos a tener que gastar horas en armar ambientes de desarrollo para nuestro equipo, o intentar sincronizar los ambientes de desarrollo cuando por ejemplo cambiamos alguna version de interprete de Ruby. Simplemente compartimos en nuestro repositorio el Dockerfile, y cada desarrollador de nuestro equipo tiene una copia idéntica del ambiente de desarrollo en su maquina.

Escalabilidad

El tiempo que tarda un container en arrancar es mucho menor al de un sistema operativo, consume muchos menos recursos. En cuanto necesitemos mas instancias de nuestra aplicación, solo levantamos unos nuevos containers y listo. Esto también es una técnica utilizada con maquinas virtuales (AWS, Heroku, Azure, etc. lo hacen), pero el costo monetario y de tiempo de levantar una instancia de un container es muchísimo menor, según la web de Docker 7x mas rápido.

En el próximo posts voy a publicar un ejemplo de como usar Docker para una aplicación Ruby on Rails + Postgres.