{"id":13446242,"url":"https://github.com/brunocascio/docker-espanol","last_synced_at":"2026-01-31T11:04:38.213Z","repository":{"id":45808164,"uuid":"49382047","full_name":"brunocascio/docker-espanol","owner":"brunocascio","description":"Un tutorial Docker en español. Basado en el libro Docker Cookbook de O'reilly","archived":false,"fork":false,"pushed_at":"2020-07-15T17:45:16.000Z","size":61,"stargazers_count":223,"open_issues_count":0,"forks_count":72,"subscribers_count":27,"default_branch":"master","last_synced_at":"2024-07-31T05:10:27.511Z","etag":null,"topics":["docker","espanol","spanish","tutorial"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brunocascio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-10T19:36:01.000Z","updated_at":"2024-07-29T04:39:59.000Z","dependencies_parsed_at":"2022-07-18T08:14:15.229Z","dependency_job_id":null,"html_url":"https://github.com/brunocascio/docker-espanol","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunocascio%2Fdocker-espanol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunocascio%2Fdocker-espanol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunocascio%2Fdocker-espanol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brunocascio%2Fdocker-espanol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brunocascio","download_url":"https://codeload.github.com/brunocascio/docker-espanol/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221812275,"owners_count":16884568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","espanol","spanish","tutorial"],"created_at":"2024-07-31T05:00:49.206Z","updated_at":"2026-01-31T11:04:38.205Z","avatar_url":"https://github.com/brunocascio.png","language":"Ruby","funding_links":[],"categories":["Uncategorized","Where to Start"],"sub_categories":["Uncategorized","Wrappers"],"readme":"# Docker\n\n---\n\n## Conceptos importantes:\n\n- Docker (daemon)\n- Docker-machine (client)\n- Docker Images\n- Docker Containers\n\n## Introducción\n\nLa idea de Docker, es la de crear aplicaciones/servicios que sean independientes y portables. Esto es, no importa que sistema operativo utilices o con que hardware cuentas, si puedes instalar docker, entonces podras correr tus contenedores en él.\nEntre las ventajas de usar docker, se encuentra la de olvidarte de instalar dependencias (ejemplo nodejs, java, python, ruby, etc) dentro de tu host o servidor y sin utilizar máquinas virtuales. A lo largo de este documento, iremos viendo el ecosistema de Docker y como todo se relaciona.\n\nDocker posibilita ir de tu maquina local a producción, con tu aplicació lista. Ya no es necesario instalar en cada servidor dependencias o depender del sistema operativo. Solo basta con tener Docker instalado.\n\n## Instalación\n\nLa instalación puede seguirse desde la [Documentación Oficial](https://docs.docker.com/engine/installation/).\n\n## Empezando\n\n### `images`\n\nMuestra las imagenes locales disponibles.\n\n```\n  $ docker images\n```\n\nPodemos eliminarlas con `docker rmi \u003cimage-name\u003e`, siempre y cuando no tenga contenedores asociados (corriendo o no).\n\n### `events`\n\nPodemos ver en tiempo real, los eventos que Docker lanza en nuestro servidor, solo basta con:\n\n```\n  $ docker events\n```\n\n### `run`\n\nCorremos un contenedor con la _imagen_ base `busybox`, que ejecuta el comando `echo hello world` dentro. Luego de esto, el contenedor se detendrá, porque de esta forma funciona como un \"job\".\n\n```\n  $ docker run busybox echo hello world\n```\n\n### `ps`\n\nMuestra los contenedores en ejecución (running).\n\n```\n  $ docker ps\n```\n\nSi lo ejecutamos luego del comando anterior, no mostrará nada porque lo anterior era solo un `build, run, die`.\n\nPara ver el historial de ejecución y los contenedores actualmente creados (corriendo o no), ejecutamos:\n\n```\n  $ docker ps -a\n```\n\nEsto nos mostrará todos los contenedores que se encuentran creados hasta el momento. Podemos borrarlos si queremos ejecutando `docker rm \u003ccontainer-id\u003e`\n\n### `run` avanzado\n\nPodemos pasar muchas opciones al comando `run`, las cuales podemos ver con:\n\n```\n  $ docker run --help\n```\n\n#### `run` interactivo\n\nProbemos de ejecutar y usar una terminal en el contenedor:\n\n```\n  $ docker run -t -i ubuntu:14.04 /bin/bash\n```\n\n- `-t`: Asigna una tty\n- `-i`: Nos comunicamos con el contenedor de modo interactivo.\n\n**NOTA:** Al salir del modo interactivo el contenedor se detendrá.\n\n#### `run` Detached Mode\n\nProblema: Ya sabemos cómo correr un contenedor de manera interactiva, pero el problema es que el mismo al terminar de ejecutar la tarea, finaliza. Si se quieren hacer contenedores que corran servicios (por ejemplo, un servidor web) el comando es el siguiente:\n\n```\n$ docker run -d -p 1234:1234 python:2.7 python -m SimpleHTTPServer 1234\n```\n\nEsto ejecuta un servidor Python (SimpleHTTPServer module), en el puerto `1234`. El argumento `-p 1234:1234` le indica a Docker que tiene que hacer un **port forwarding** del contenedor hacia el puerto `1234` de la máquina host.\n\nAhora podemos abrir un browser en la dirección `http://localhost:1234`.\n\n**Algo más**\n\nLa opción `-d` hace que el contenedor corra en segundo plano. Esto nos permite ejecutar comandos sobre el mismo en cualquier momento mientras esté en ejecución. Por ejemplo:\n\n`$ docker exec -ti \u003ccontainer-id\u003e /bin/bash`\n\nAquí simplemente se abre una `tty` en modo `interativo`. Podrían hacerse otras cosas como cambiar el _working directory_, setear _variables de entorno_, etc. La lista completa puede verse [acá](https://docs.docker.com/reference/run/)\n\n## Ciclo de vida de un contenedor\n\nHasta ahora vimos cómo ejecutar un contenedor tanto en foreground como en background (detached). Ahora veremos cómo manejar el ciclo completo de vida de un contenedor.\nDocker provee de comandos como `create` , `start`, `stop`, `kill` , y `rm`. En todos ellos podría pasarse el argumento `-h` para ver las opciones disponibles.\nEjemplo: `docker create -h`\n\nMás arriba vimos cómo correr un contenedor en segundo plano (detached). Ahora veremos en el mismo ejemplo, pero con el comando `create`. La única diferencia que esta vez no especificaremos la opción `-d`. Una vez preparado, necesitaremos lanzar el contenedor con `docker start`.\n\nEjemplo:\n\n```\n$ docker create -P --expose=8001 python:2.7 python -m SimpleHTTPServer 8001\n  a842945e2414132011ae704b0c4a4184acc4016d199dfd4e7181c9b89092de13\n$ docker ps -a\n  CONTAINER ID IMAGE      COMMAND              CREATED       ... NAMES\n  a842945e2414 python:2.7 \"python -m SimpleHTT 8 seconds ago ... fervent_hodgkin\n$ docker start a842945e2414\n  a842945e2414\n$ docker ps\n  CONTAINER ID IMAGE      COMMAND              ... NAMES\n  a842945e2414 python:2.7 \"python -m SimpleHTT ... fervent_hodgkin\n```\n\nSiguiendo el ejemplo, para detener el contenedor se puede ejecutar cualquiera de los siguientes comandos:\n\n```bash\n  docker kill a842945e2414 # (envía SIGKILL)\n```\n\n```bash\n  docker stop a842945e2414 # (envía SIGTERM).\n```\n\nAsí mismo, pueden reiniciarse:\n\n```bash\n  docker restart a842945e2414\n```\n\no destruirse:\n\n```bash\n  docker rm a842945e2414\n```\n\n## Crear una imagen Docker con un Dockerfile\n\n**Problema:**\n\nYa entendemos cómo se descargan las imágenes del _Docker Registry_. ¿Qué pasa si ahora quisiéramos armar nuestras propias imagenes? (Para compartir, obvio 😜)\n\n**Solución:**\n\nUsando un [Dockerfile](https://docs.docker.com/engine/reference/builder/). Un `Dockerfile` es un archivo de texto, que describe los pasos (secuenciales) a seguir para preparar una imagen Docker. Esto incluye instalación de paquetes, creación de directorios, definición de variables de entorno, ETC.\nToda imagen que creemos, parte de una _base image_. Como en otro de los ejemplos, usaremos la imagen [busybox](https://busybox.net/about.html) la cual combina utilidades UNIX en un único y simple ejecutable.\n\n**Comenzando:**\n\nCrearemos nuestra propia imagen con la imagen base _busybox_ y setearemos sólo una variable de entorno para mostrar el funcionamiento.\n\nEl siguiente comando crea el directorio pepe y se posiciona dentro de él:\n\n`$ mkdir pepe \u0026\u0026 cd $_`\n\nCreamos el Dockerfile:\n\n`$ touch Dockerfile`\n\nEscribimos las siguientes líneas dentro del `Dockerfile`:\n\n```Dockerfile\nFROM busybox\n\nENV foo=bar\n```\n\nHecho esto, haremos un `build` de la imagen con el nombre `my-busybox`:\n\n`$ docker build -t my-busybox .` (Chequear el . al final)\n\nSi todo salió bien al hacer `docker images`, deberíamos encontrar nuestra imagen. ¡WALÁ!\n\n## Ejemplo Real: Wordpress Dockerizado.\n\n_Es un setup básico, no lo usaría en producción :)_\n\nPara esto usaremos MySql y HTTPD (apache o nginx).\n\n**Problema:**\n\nComo Docker ejecuta procesos en _foreground_, necesitamos encontrar la forma de ejecutar varios de estos simultáneamente. La directiva `CMD` que veremos más adelante, sólo ejecutará una instrucción. Es decir, si tenemos varios `CMD` dentro de un _Dockerfile_, ejecutará sólo el último.\n\n**Solución:**\n\nUsando [Supervisor](http://supervisord.org/index.html) para monitorear y ejecutar MySql y HTTPD. Supervisor se encarga de controlar varios procesos y se ejecuta como cualquier otro programa.\n\nVeremos diferentes formas de hacer esto. En principio crearemos todo dentro de un único contenedor, pero luego explotaremos al máximo los principios y características de Docker para hacerlo, por ejemplo separar servicios en diferentes contenedores y _linkearlos_.\n\n### Usando Supervisor y en un único contenedor\n\nCreamos el Dockerfile, con este contenido:\n\n```Dockerfile\n  # Imagen Base\n  FROM ubuntu:14.04\n\n  # Instalamos dependencias\n    # apache2: Servidor Web\n    # php5: Lenguaje de programacion PHP\n    # php5-mysql: Driver de MySql para PHP\n    # supervisor: Lanzadaror y Monitor de procesos\n    # wget: Utilidad para obtener archivos via HTTP\n  RUN apt-get update \u0026\u0026 apt-get -y install \\\n    apache2 \\\n    php5 \\\n    php5-mysql \\\n    supervisor \\\n    wget\n\n  # mysql-server se instala con intervención del usuario,\n  # pero como no es modo interactivo lo que hacemos es setearle las variables\n  # con un valor.\n  # Para simplificar hemos usado como usuario y contraseña de mysql 'root'\n  RUN echo 'mysql-server mysql-server/root_password password root' | \\\n    debconf-set-selections \u0026\u0026 \\\n    echo 'mysql-server mysql-server/root_password_again password root' | \\\n    debconf-set-selections\n\n  # Procedemos ahora sí, a instalar mysql-server\n  RUN apt-get install -qqy mysql-server\n\n  # Preparamos Wordpress\n    # Obtenemos la última versión\n    # Descomprimimos\n    # Copiamos el contenido dentro del root del servidor\n    # Removemos el viejo index.html (mensaje de bienvenida de apache)\n  RUN wget http://wordpress.org/latest.tar.gz \u0026\u0026 \\\n    tar xzvf latest.tar.gz \u0026\u0026 \\\n    cp -R ./wordpress/* /var/www/html \u0026\u0026 \\\n    rm /var/www/html/index.html\n\n  # De esto se encargaría supervisor, pero como necesitamos crear la base de datos\n  # ejecutamos a mysql en background y creamos la base de datos llamada wordpress\n  RUN (/usr/bin/mysqld_safe \u0026); sleep 5; mysqladmin -u root -proot create wordpress\n\n  # Reemplazamos el archivo wp-config.php (más abajo lo creamos) a la carpeta de wordpress\n  # Este archivo contiene la configuración de nuestro sitio\n  COPY wp-config.php /var/www/html/wp-config.php\n\n  # Copiamos el archivo de configuración de supervisor (más abajo lo creamos)\n  COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf\n\n  # Le decimos al contenedor que tiene que hacer accesible al puerto 80 (en el que corre HTTPD)\n  # para así nosotros poder acceder al mismo desde fuera\n  EXPOSE 80\n\n  # Lanzamos Supervisor como proceso Foreground de Docker\n  # Este se encargará de lanzar simultaneamente los demás :D\n  CMD [\"/usr/bin/supervisord\"]\n```\n\nCreamos el archivo `supervisord.conf` con este contenido:\n\n```\n[supervisord]\nnodaemon=true\n\n[program:mysqld]\ncommand=/usr/bin/mysqld_safe\nautostart=true\nautorestart=true\nuser=root\n\n[program:httpd]\ncommand=/bin/bash -c \"rm -rf /run/httpd/* \u0026\u0026 /usr/sbin/apachectl -D FOREGROUND\"\n```\n\nCreamos el archivo `wp-config.php` con este contenido:\n\n```php\n  \u003c?php\n  /**\n   * The base configurations of the WordPress.\n   *\n   * This file has the following configurations: MySQL settings, Table Prefix,\n   * Secret Keys, and ABSPATH. You can find more information by visiting\n   * {@link http://codex.wordpress.org/Editing_wp-config.php Editing wp-config.php}\n   * Codex page. You can get the MySQL settings from your web host.\n   *\n   * This file is used by the wp-config.php creation script during the\n   * installation. You don't have to use the web site, you can just copy this file\n   * to \"wp-config.php\" and fill in the values.\n   *\n   * @package WordPress\n   */\n\n  // ** MySQL settings - You can get this info from your web host ** //\n  /** The name of the database for WordPress */\n  define('DB_NAME', 'wordpress');\n\n  /** MySQL database username */\n  define('DB_USER', 'root');\n\n  /** MySQL database password */\n  define('DB_PASSWORD', 'root');\n\n  /** MySQL hostname */\n  define('DB_HOST', 'localhost');\n\n  /** Database Charset to use in creating database tables. */\n  define('DB_CHARSET', 'utf8');\n\n  /** The Database Collate type. Don't change this if in doubt. */\n  define('DB_COLLATE', '');\n\n  /**#@+\n   * Authentication Unique Keys and Salts.\n   *\n   * Change these to different unique phrases!\n   * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}\n   * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.\n   *\n   * @since 2.6.0\n   */\n  define('AUTH_KEY',         'put your unique phrase here');\n  define('SECURE_AUTH_KEY',  'put your unique phrase here');\n  define('LOGGED_IN_KEY',    'put your unique phrase here');\n  define('NONCE_KEY',        'put your unique phrase here');\n  define('AUTH_SALT',        'put your unique phrase here');\n  define('SECURE_AUTH_SALT', 'put your unique phrase here');\n  define('LOGGED_IN_SALT',   'put your unique phrase here');\n  define('NONCE_SALT',       'put your unique phrase here');\n\n  /**#@-*/\n\n  /**\n   * WordPress Database Table prefix.\n   *\n   * You can have multiple installations in one database if you give each a unique\n   * prefix. Only numbers, letters, and underscores please!\n   */\n  $table_prefix  = 'wp_';\n\n  /**\n   * For developers: WordPress debugging mode.\n   *\n   * Change this to true to enable the display of notices during development.\n   * It is strongly recommended that plugin and theme developers use WP_DEBUG\n   * in their development environments.\n   */\n  define('WP_DEBUG', false);\n\n  /* That's all, stop editing! Happy blogging. */\n\n  /** Absolute path to the WordPress directory. */\n  if ( !defined('ABSPATH') )\n  \tdefine('ABSPATH', dirname(__FILE__) . '/');\n\n  /** Sets up WordPress vars and included files. */\n  require_once(ABSPATH . 'wp-settings.php');\n```\n\nAhora sólo queda realizar el build de nuestra imagen y luego ejecutar un contenedor :)\n\n```\n$ docker build -t wordpress .\n$ docker run -d -p 80:80 wordpress\n```\n\nUna vez funcionando, ingresando en `http://\u003cIP_OF_DOCKER_HOST\u003e` deberíamos visualizar la página de instalación de wordpress.\n\n**Nota:**\n\nUsar Supervisor para ejecutar varios servicios dentro del mismo contenedor, podría trabajar perfectamente, pero es mejor usar múltiples contenedores. Estos proveen del aislamiento (isolation) entre otras bondades de Docker, y nos ayuda además a crear una aplicación basada en [microservicios](http://bit.ly/building-microservices). Por último, también esto nos ayuda a escalar y a recuperarnos de posibles fallas.\n\n## Corriendo Wordpress usando 2 contenedores linkeados.\n\n**Problema:**\n\nHasta ahora ejecutamos una instancia de wordpress con su servidor y su base de datos, en un mismo contenedor. El problema es que no explotamos al máximo a Docker, y no mantenemos tampoco el concepto de _Separation of concerns_. Necesitamos desacoplar el contenedor lo más fino posible.\n\n**Solución:**\n\nUsar 2 contenedores. Uno para Wordpress y otro para MySql. Luego se interconectarán mediante la opción de docker `--link`.\n\n**Manos a la obra:**\n\nPara este ejemplo usaremos las imágenes docker oficiales de wordpress y mysql.\n\n```\n$ docker pull wordpress:latest\n$ docker pull mysql:latest\n```\n\n**Ejecutamos un contenedor MySql**\n\n```\n$ docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker \\\n                          -e MYSQL_DATABASE=wordpress \\\n                          -e MYSQL_USER=wordpress \\\n                          -e MYSQL_PASSWORD=wordpresspwd \\\n                          -v /db/mysql:/var/lib/mysql \\\n                          -d mysql\n```\n\nNOTA: Aquí hay nuevas opciones:\n\n- `-e` es para setear variables de entorno. Esas variables están definidas dentro del Dockerfile de MySql, por lo que nosotros le damos valor, para que el contenedor a ejecutar, use esos datos.\n- `-v` es para montar un volumen entre el host y el contenedor. En este caso en el host se populará el volumen `/db/mysql/` con la info de `/var/lib/mysql`.\n  - Los volúmenes tienen diferentes usos:\n    - Se crean cuando se inicializa el contenedor\n    - Compartir información entre diferentes contenedores\n    - Mantener la info luego de haber borrado el contenedor\n    - Cambios en los volúmenes son directamente aplicados (no hay que hacer nada adicional con el contenedor para actualizar)\n    - Los cambios de un volumen no se incluirán en la actualización de la imagen\n\n**Ejecutamos y linkeamos a wordpress**\n\n```\n$ docker run --name wordpress --link mysqlwp:mysql -p 80:80 \\\n                              -e WORDPRESS_DB_NAME=wordpress \\\n                              -e WORDPRESS_DB_USER=wordpress \\\n                              -e WORDPRESS_DB_PASSWORD=wordpresspwd \\\n                              -d wordpress\n```\n\nNOTA: La imagen de wordpress, expone el puerto 80 y lo que hacemos es mapearlo con el 80 del nuestro Host. Como en la imagen de MySql, en wordpress tambien contamos con algunas variables de entorno, éstas para la configuración del mismo. Básicamente seteamos las credenciales de la base de datos anteriormente creada, para que wordpress use las mismas.\n\n## Haciendo backups de la base de datos de un contenedor\n\n**Problema:**\n\nTenemos un contenedor de mysql ejecutando, pero necesitamos hacer un backup de la base de datos que se ejecuta dentro del contenedor.\n\n**Solucion:**\n\nUsar el comando `docker exec` para ejecutar en el contenedor MySql el comando `mysqldump`\n\nChequeamos en nuestro host que existe la carpeta `/db/mysql`\n\n`$ ls /db/mysql`\n\nAhora, para hacer un backup de la base de datos de ese contenedor ejecutamos:\n\n`docker exec mysqlwp mysqldump --all-databases --password=wordpressdocker \u003e wordpress.backup`\n\nAhora ejecutamos `$ ls` y veremos el archivo `wordpress.backup` :)\n\n## Compartir información entre el Docker Host y los contenedores\n\n**Problema:**\n\n- Tenemos información local, que queremos que este disponible en un contenedor (por ejemplo, en desarrollo, el codigo de tu aplicación).\n- Tenemos informacion del contenedor que necesitamos guardar en el host (por ejemplo una base de datos)\n\n**Solución:**\n\nUsando volúmenes (opción `-v` antes vista) para montar uno entre el host y el contenedor.\n\nPor ejemplo, si queremos compartir nuestro directorio de trabajo, con un directorio particular del contenedor podríamos hacer:\n\n```\n  docker run -ti -v \"$PWD\":/pepe ubuntu:14.04 /bin/bash\n```\n\nLo que hicimos con ese comando, fue montar como volumen nuestro directorio actual con el directorio `/pepe` en el contenedor (OJO, `/` referencia al root del filesystem). Además, como vimos antes con las opciones `-ti` levantamos un tty y de modo interativo ejecutamos una instancia de bash.\n\n**Algo más:**\n\nDocker provee de un comando `docker inspect` que sirve para observar la información de un contenedor.\n\n`docker inspect -f {{.Mounts}} \u003ccontainer-id\u003e`\n\nCon el comando anterior, filtramos de toda la información, solo los puntos de montaje. Como salida obtendremos algo como:\n\n`[{ /path/to/pwd /pepe true}]`\n\n**Disclaimer**\n\nDocker tiene un [apartado sobre volúmenes](https://docs.docker.com/storage/) que es muy importante conocer.\n\nCada tipo de volumen (bind, tmpfs, named) tienen usos distintos, y hacer uso del equivocado podria traernos problemas de performance o aun peor, perdida de datos si no usamos los comandos adecuadamente.\n\nSi te encontras con problemas de performance en OSX, probablemente esto te sea util [docker sync](http://docker-sync.io/)\n\n## Compartir información entre contenedores\n\n**Problema:**\n\nYa sabemos cómo montar un volumen de nuestro Host en un contenedor. Pero ahora quisiéramos compartir ese volumen definido en el contenedor con otros contenedores.\n\n**Solución:**\n\nUsando _data containers_. Cuando queremos montar un volúmen en un contenedor lo que hacemos es con el argumento `-v` decirle el directorio _X_ del host que debe montarse en el el path _Y_ del contenedor.\nEl volúmen especificado se crea como de lectura-escritura dentro del contenedor y no como las capas de sólo lectura usadas para crear el contenedor, pudiéndose modificar también desde la máquina host.\n\n```\n$ docker run -ti --name=cont1 -v /pepe ubuntu:14.04 /bin/bash\nroot@cont1:/# touch /pepe/foobar\nroot@cont1:/# ls pepe/\nfoobar\nroot@cont1:/# exit\nexit\nbash-4.3$ docker inspect -f {{.Mounts}} cont1\n[{dbba7caf8d07b862b61b39... /var/lib/docker/volumes/dbba7caf8d07b862b61b39... \\\n/_data /pepe local true}]\n$ sudo ls /var/lib/docker/volumes/dbba7caf8d07b862b61b39...\nfoobar\n```\n\nY ahora ejecutamos otro contenedor con el volumen anteriormente creado.\n\n```\n$ docker run --volumes-from=cont1 --name=cont2 ubuntu:14.04\n$ docker inspect -f {{.Mounts}} cont2\n[{4ee1d9e3d453e843819c6ff... /var/lib/docker/volumes/4ee1d9e3d453e843819c6ff... \\\n/_data /pepe local true]\n```\n\n## Copiando datos entre el host desde y para los contenedores\n\n**Problema:**\n\nTenemos un contenedor que no tiene volúmenes configurados, y queremos copiar archivos desde y en el contenedor.\n\n**Solución:**\n\nUsando `docker cp` para pasar información desde y para un contenedor en ejecución.\n\nPodemos ver más opciones con `docker cp --help` o sólo `docker cp`.\n\nPor ejemplo, para pasar archivos desde el docker host hacia el contenedor:\n\n```\n$ docker run -d --name testcopy ubuntu:14.04 sleep 360\n$ touch pepe.txt\n$ docker cp pepe.txt testcopy:/root/file.txt\n```\n\nY pasando del contenedor hacia el docker host:\n\n```\n$ docker cp testcopy:/root/file.txt pepe.txt\n$ ls\npepe.txt\n```\n\n## Crear y compartir `Docker Images`\n\nDespués de crear varios contenedores, tal vez quisiéramos crear nuestras propias imágenes también. Cuando iniciamos un contenedor, al mismo lo iniciamos desde una imagen base. Una vez con el contenedor en ejecución nosotros podríamos hacer cambios, por ejemplo, instalarle ciertas librerías o dependencias (ejemplo, correr `apt install htop vim git` dentro de un contenedor que tiene de imagen base, `ubuntu`).\nLuego de haber ejecutado este comando, el contenedor ha modificado su filesystem. Nosotros a futuro tal vez quisiéramos ejecutar contenedores iguales al anterior, por lo que Docker nos provee del comando `commit` para, a partir de un contenedor, crear una imagen.\nDocker mantiene las diferencias entre la imagen base y la que se quiere crear, creando una nueva _layer_ usando [UnionFS](https://es.wikipedia.org/wiki/UnionFS). Similar a _git_.\n\nCrearemos un contenedor de _ubuntu_, y al mismo le actualizaremos la lista de repositorios. Luego de ello, haremos un `docker commit`, para definir la nueva imagen para mantener una imagen mas actualizada.\n\n```\n$ docker run -t -i --name=contenedorPrueba ubuntu:14.04 /bin/bash\nroot@69079aaaaab1:/# apt update\n```\n\nCuando salgamos de este contenedor, el mismo se detendrá, pero seguirá estando disponibles a menos que lo eliminemos explícitamente con `docker rm`. Ahora commitiemos el contenedor, para crear una nueva imagen.\n\n```\n$ docker commit contenedorPrueba ubuntu:update\n13132d42da3cc40e8d8b4601a7e2f4dbf198e9d72e37e19ee1986c280ffcb97c\n$ docker images\nREPOSITORY    TAG     IMAGE ID      CREATED          VIRTUAL SIZE\nubuntu        update  13132d42da3c  5 days ago  ...  213 MB\n```\n\n**NOTA:** Esto `ubuntu:update` especifica `\u003cnombre_imagen\u003e:\u003ctag_del_commit\u003e`.\n\nLuego ya podremos lanzar contenedores basados en la nueva imagen `ubuntu:update`.\n\n**ADICIONAL**\n\nPodemos chequear las diferencias con `docker diff`.\n\n```\n$ docker diff contenedorPrueba\nC /root\nA /root/.bash_history\nC /tmp\nC /var\nC /var/cache\nC /var/cache/apt\nD /var/cache/apt/pkgcache.bin\nD /var/cache/apt/srcpkgcache.bin\nC /var/lib\nC /var/lib/apt\nC /var/lib/apt/lists\n...\n```\n\n## Guardando Images y Containers como archivos .tar para compartir\n\n**Problema:** Tenemos creados imagenes o tenemos contenedores que queremos mantener y nos gustaría compartirlo con nuestros colaboradores.\n\n**Solución:**\n\n- Para las `images`: Usar los comandos `save` y `load` para crear el archivo comprimido de la imagen anteriormente creada.\n- Para los `containers`: Usar los comandos `import` y `export`.\n\nComencemos con un `container` creado y exportándolo en un archivo `.tar` (tarball).\n\n```\n  $ docker ps -a\n  CONTAINER ID  IMAGE         COMMAND       CREATED         ...   NAMES\n  77d9619a7a71  ubuntu:14.04  \"/bin/bash\"   10 seconds ago  ...   high_shockley\n  $ docker export 77d9619a7a71 \u003e update.tar\n  $ ls\n  update.tar\n```\n\nSe puede hacer `commit` de este contenedor como una nueva imagen local, pero tambien se podría usar el comando `import`:\n\n```\n  $ docker import - update \u003c update.tar\n  157bcbb5fdfce0e7c10ef67ebdba737a491214708a5f266a3c74aa6b0cfde078\n  $ docker images\n  REPOSITORY  TAG     IMAGE ID      ...   VIRTUAL SIZE\n  update      latest  157bcbb5fdfc  ...   188.1 MB\n```\n\nSi se quiere compartir esta imagen con uno de sus colaboradores, podría subirse el tarball a un webserver y decirle al colaborar que descarga tal, y use el comando `import` en su Docker Host.\nSi se prefiere usar imagenes que ya se han comitiado, se puede usar los comandos `load` y `save` mencionados anteriormente.\n\nEntonces, **¿Cuál es la diferencia?**\n\nLos 2 métodos son similares; La diferencia está en que guardando una imagen mantenemos el historial de cambios, y exportándola como contenedor NO.\n\n_A mi punto de vista, tal vez lo mejor sería sólo mantener los cambios cuando ya es algo en producción y deseamos hacer actualización de software. Por ejemplo del SO o de APACHE/NGINX, donde si ocurre una falla o incompatibilidad, podría volverse atrás. En cambio mientras estamos haciendo el desarrollo, mantener los cambios tal vez no sea tan importante._\n\n## Escribiendo nuestro primer DockerFile\n\n**Problema:**\n\nEjecutar contenedores en modo interactivo, hacer algunos cambios y para luego comitear estos en una nueva imagen, funciona bien. Pero en la mayoría de los casos, tal vez quieras automatizar este proceso de creación de nuestra propia imagen y compartir estos pasos con otros.\n\n**Solución:**\n\nPara automatizar el proceso de creación de imágenes Docker, prepararemos tales paso en un archivo de manifiesto, llamado **Dockerfile**.\nEste archivo de texto está compuesto por una serie de instrucciones que describe cuál es la _imagen base_ de la que el nuevo contenedor se basará, cuáles pasos necesitan llevarse a cabo para instalar las _dependencias_ de la aplicación, cuáles _archivos_ necesitan estar presentes en la imagen, cuáles puertos serán _expuestos_ por el contenedor y cuáles _comando_ ejecutar cuando se ejecuta el contenedor, entre otras cosas.\n\nPara ilustrar esto, crearemos un simple Dockerfile. La **imagen** resultante nos permitirá crear un contenedor que ejecuta el comando `/bin/echo`.\n\n```Dockerfile\nFROM ubuntu:14.04\n\nENTRYPOINT [\"/bin/echo\"]\n```\n\nLa instrucción `FROM` dice de cuál **imagen base** partimos para crear la nuestra. En este caso `ubuntu:14.04`, que la primera vez será descargada del repositorio del _Docker Hub_.\n\nLa instrucción `ENTRYPOINT` dice cuál es el comando a ejecutar cuando el contenedor basado en esta imagen, sea ejecutado.\n\nPara hacer `build` de esta imagen, ejecutamos `docker build .`\n\nHecho el build, ejecutamos un nuevo contenedor a partir de esta imagen:\n\n`docker images`\n\n```\nREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\n\u003cnone\u003e              \u003cnone\u003e              99fac58824c2        5 minutes ago       187.9 MB\n\n```\n\n```\ndocker run 99fac58824c2 Hi Docker!\nHi Docker !\n```\n\nLo que hemos hecho es ejecutar un contenedor a partir de la imagen previamente creada, pasándole como argumento `Hi Docker!`.\nEl contenedor al ejecutarse, corrió el comando definido por el `ENTRYPOINT`, seguido por el argumento anteriormente mencionado.\nUna vez que el comando ha **finalizado** (la tarea finaliza), el contenedor es finalizado también.\n\nTambien podemos usar la instrucción `CMD` en un Dockerfile. Esta tiene la ventaja que se puede sobreescribir cuando este se ejecuta, pasándolo como argumento. Por ejemplo:\n\n```Dockerfile\nFROM ubuntu:14.04\n\nCMD [\"/bin/echo\" , \"Hi Docker !\"]\n```\n\nConstruimos la nueva imagen:\n\n`docker build .`\n\nEjecutamos un contenedor a partir de esta:\n\n```\ndocker run 99fac58824c2\nHi Docker!\n```\n\nY ahora sobreescribiendo el comando:\n\n```\ndocker run 99fac58824c2 /bin/date\nThu Mar 17 00:14:00 UTC 2016\n```\n\nSi el Dockerfile utiliza la instrucción `ENTRYPOINT` y necesitamos hacer override, se le puede pasar la opción `--entrypoint` al `docker run`.\n\nTenemos una imagen creada, pero como verán no tiene un `tag` y siempre nos referimos a ella por su `IMAGE ID`.\nPara esto podemos hacer un rebuild usando la opción `-t`.\n\n```\n$ docker build -t ubuntu-echo:1.0.0 .\n...\n...\nREPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE\nubuntu-echo        1.0.0 \t        99fac58824c2        About an hour ago   187.9 MB\n...\n```\n\nPodemos colocarle el nombre que querramos, pero siempre es mejor seguir las convenciones :)\n\n`\u003cname-of-recipe\u003e:\u003cversion-of-recipe\u003e`\n\nEl comando `build` tiene una serie de opciones configurables y pueden verse con la opcion -h\n\n```\n$ docker build -h\n\nUsage:\tdocker build [OPTIONS] PATH | URL | -\n\nBuild an image from a Dockerfile\n\n  --build-arg=[]                  Set build-time variables\n  --cpu-shares=0                  CPU shares (relative weight)\n  --cgroup-parent=                Optional parent cgroup for the container\n  --cpu-period=0                  Limit the CPU CFS (Completely Fair Scheduler) period\n  --cpu-quota=0                   Limit the CPU CFS (Completely Fair Scheduler) quota\n  --cpuset-cpus=                  CPUs in which to allow execution (0-3, 0,1)\n  --cpuset-mems=                  MEMs in which to allow execution (0-3, 0,1)\n  --disable-content-trust=true    Skip image verification\n  -f, --file=                     Name of the Dockerfile (Default is 'PATH/Dockerfile')\n  --force-rm=false                Always remove intermediate containers\n  --help=false                    Print usage\n  -m, --memory=                   Memory limit\n  --memory-swap=                  Total memory (memory + swap), '-1' to disable swap\n  --no-cache=false                Do not use cache when building the image\n  --pull=false                    Always attempt to pull a newer version of the image\n  -q, --quiet=false               Suppress the verbose output generated by the containers\n  --rm=true                       Remove intermediate containers after a successful build\n  -t, --tag=                      Repository name (and optionally a tag) for the image\n  --ulimit=[]                     Ulimit options\n```\n\n## Empaquetando una aplicación Flask en un contenedor\n\n**Problema**\n\nTenemos una aplicación web buildeada en **Flask** corriendo en nuestro Ubuntu 14.04 y queremos correrla en un contenedor.\n\n**Solución**\n\nComo un ejemplo, vamos a usar una simple aplicacion [Flask Hello World](http://flask.pocoo.org/)\n\nPara instalar el modulo Flask simplemente corremos este comando\n\n`$ pip install Flask`\n\n```\n#!/usr/bin/env python\n\nfrom flask import Flask\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef hello():\n  return \"Hello World!\"\n\nif __name__ == \"__main__\":\n  app.run(host='0.0.0.0', port=5000)\n```\n\nPara tener esta aplicación corriendo en un contenedor Docker, necesitamos escribir un `Dockerfile` que instale las dependencias de este framework (comando `RUN`), y poder correr nuesta app. También necesitamos exponer el puerto del contenedor (comando `EXPOSE`).\nTambién necesitamos mover nuestra aplicación al Filesystem del contenedor (comando `ADD`).\nEl Dockerfile quedaría de la siguiente forma:\n\n```Dockerfile\n  FROM ubuntu:14.04\n\n  # Actualizamos repositorios e instalamos dependencias.\n  RUN apt-get update\n  RUN apt-get install -y python python-pip\n  RUN apt clean all\n  RUN pip install flask\n\n  # Agregamos nuestra aplicación al Filesystem del contenedor.\n  ADD hello.py /tmp/hello.py\n\n  # Exponemos el puerto del contenedor\n  EXPOSE 5000\n\n  # Comando por default que se ejecuta cuando se corre el contenedor\n  CMD [\"python\",\"/tmp/hello.py\"]\n```\n\n**Nota**: Este Dockerfile no está optimizado, intencionalmente. Para optimizarlo lo veremos más adelante, pero esto sólo es para entender lo básico.\n\nEl comando `RUN` permite ejecutar comandos específicos durante el _build_ de la imagen del contenedor.\nPara copiar nuestra aplicación dentro de la imagen del contenedor, usamos el comando `ADD`. En nuestro caso, copia el archivo `hello.py` al directorio `/tmp` de la imagen del contenedor.\nLa aplicación usa el puerto `5000`, y tenemos que _exponer_ este puerto al Docker Host.\nFinalmente, el comando `CMD` especifica que el contenedor debe ejecutar `python /tmp/hello.py` cuando se ejecute.\n\nProcedemos a hacer _build_ de la imagen.\n\n`$ docker build -t flask .`\n\nEsto creó una imagen Docker _flask_:\n\n```\n$ docker images\nREPOSITORY    TAG       IMAGE ID        CREATED         VIRTUAL SIZE\nflask         latest    d381310506ed    3 seconds ago   354.6 MB\n...\n```\n\nPara correr esta aplicación usaremos la opción `-d`, la cual _daemonizará_ el contenedor. Tambien pasaremos el argumento `-P` para decirle a Docker que elija un puerto en el _Docker Host_ para _forwardear_ al puerto expuesto por el contenedor.\n\n```\n$ docker run -d -P flask\n5ac72ed12a72f0e2bec0001b3e78f11660905d20f40e670d42aee292263cb890\n```\n\n```\n  $ docker ps\n  CONTAINER ID    IMAGE           COMMAND                  ...   PORTS\n  5ac72ed12a72    flask:latest    \"python /tmp/hello.py    ...   0.0.0.0:49153-\u003e5000/tcp\n```\n\nEl contenedor retornado, está _daemonizado_ y no con nosotros logueados en una shell interativa dentro. La sección PORTS nos muestra el mapeo de puertos del contenedor en cuestión. En este caso mapea el puerto 49153 del **Docker Host** al puerto 5000 del **contenedor**. Si ahora ingresamos en [http://localhost:49153](http://localhost:49153), deberíamos ver el mensaje `hello world!`.\n\n**Nota:** Notar que no se le pasó un comando a ejecutar en el comando `run`, esto se debe a que ejecutará el `CMD` definido en el Dockerfile. También podriamos sobreescribir el comando, por ejemplo:\n\n```\n$ docker run -t -i -P flask /bin/bash\nroot@fc1514ced93e:/# ls -l /tmp\ntotal 4\n-rw-r--r-- 1 root root 194 Dec 8 13:41 hello.py\nroot@fc1514ced93e:/#\n```\n\n### Optimizando el Dockerfile siguiendo buenas prácticas\n\n**Problema**\n\nSe quiere seguir las buenas prácticas para crear Dockerfiles y optimizar las imágenes Docker.\n\n**Solución**\n\nDocker expone en su documentación [una sección de buenas prácticas](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/) para ecribir Dockerfiles. Estas prácticas nos ayudarán a crear imágenes de forma más eficiente, modulares y con menor esfuerzo.\n\nEstas son algunas instrucciones para crear buenas `Docker Images`.\n\n1. Ejecutar un único proceso por contenedor. De todas formas podríamos correr multiples procesos por contenedor, como se vió cuando usamos `supervisor`. En este caso, `supervisor` es el único proceso de cara al contenedor, pero éste levanta internamente otros procesos. Seguir la práctica de un único proceso por contenedor, nos permite hacer aplicaciones desacopladas que podrían escalar. Esto nos permite además usar _container links_ u otras técnicas de _container networking_ que veremos más adelante.\n\n2. No asumir que nuestros contenedores estarán siempre corriendo; Estos son efímeros y serán parados y reiniciados. Se debería tratarlos como entidades inmutables, lo que significa que no deberíamos modificarlos mientras están en ejecución, sino modificar el Dockerfile reconstruir la imagen y levantar un contenedor con esa imagen actualizada.\n   Por lo tanto, se recomienda manejar datos y configuraciones de ejecución fuera del contenedor y por lo tanto de su imagen.\n   Para esto, usamos `Docker Volumes`.\n\n3. Usar un archivo `.dockerignore`. Cuando creamos imágenes, Docker copiará el contenido del _working directory_ donde se encuentra el Dockerfile, dentro de la imagen. Con los archivos `.dockerignore` obtenemos un funcionamiento como el `.gitignore` y básicamente lo que logramos es excluir archivos (basura o sensibles) que no queremos que estén dentro de la imagen. El uso del `.dockerignore` es opcional, pero si no lo usamos, aseguremonos de copiar lo mínimo y necesario. Podemos chequear la sintaxis del mismo en este [link](https://docs.docker.com/engine/reference/builder/#dockerignore-file).\n\n4. Usar imágenes oficiales del Docker Hub, en lugar de escribir las nuestras desde cero. Estas imágenes están mantenidas por quienes son las empresas autoras de ese software. También podemos usar `ONBUILD images`, para simplicar el proceso de creación de nuestras imágenes.\n\n5. Finalmente, y de los más importantes, minimizar el número de capas de nuestras imágenes usando la caché de imagen. Docker usa [union filesystems](https://es.wikipedia.org/wiki/UnionFS) para almacenar las imágenes. Esto quiere decir que cada imagen se hace a partir de una imagen base más una colección de _diffs_ que agregan los cambios requeridos. Cada _diff_ representa una capa adicional en una imagen. Esto tiene un impacto directo en como nosotros escribimos nuestro Dockerfile y las directivas que usamos.\n   En la sección siguiente veremos este punto.\n\nCon estos puntos, haremos unos pequeños cambios en la imagen creada en la sección anterior:\n\nTenemos el Dockerfle de esta forma:\n\n```Dockerfile\n  FROM ubuntu:14.04\n\n  # Actualizamos repositorios e instalamos dependencias.\n  RUN apt-get update\n  RUN apt-get install -y python python-pip\n  RUN apt clean all\n  RUN pip install flask\n\n  # Agregamos nuestra aplicación al Filesystem del contenedor.\n  ADD hello.py /tmp/hello.py\n\n  # Exponemos el puerto del contenedor\n  EXPOSE 5000\n\n  # Comando por default que se ejecuta cuando se corre el contenedor\n  CMD [\"python\",\"/tmp/hello.py\"]\n```\n\nAplicamos unos cambios:\n\n```Dockerfile\n  FROM ubuntu:14.04\n\n  RUN apt-get update \u0026\u0026 apt-get install -y \\\n    python\n    python-pip\n\n  RUN pip install flask\n\n  COPY hello.py /tmp/hello.py\n\n  EXPOSE 5000\n\n  CMD [\"python\",\"/tmp/hello.py\"]\n```\n\nUsar múltiples comandos `RUN` es una mala práctica, ya que genera una nueva capa por cada uno. También cambiamos el comando `ADD` por `COPY` ya que `ADD` es para operaciones de copiado más complejas, y nosotros sólo copiamos de manera simple.\n\nAun así podríamos aplicar más optimizaciones como la siguiente:\n\n```Dockerfile\n  FROM python:2.7.10\n\n  RUN pip install flask\n\n  COPY hello.py /tmp/hello.py\n\n  EXPOSE 5000\n\n  CMD [\"python\",\"/tmp/hello.py\"]\n```\n\nEntre los cambios, se puede ver que cambiamos a `ubuntu` por `python` como _imagen base_ (aplicando el punto `2.` de optimizaciones). Eliminando toda la instalación de dependencias para python.\nEstas optimizaciones aún podrían ser más optimizables como, por ejemplo, usar la imagen base de `Flask`, pero la idea es que se note la diferencia entre un `Dockerfile` y otro optimizado.\n\n## Configuración avanzada de red\n\nCon todo lo descrito anteriormente ya se puede empezar a trabajar con Docker y tener una visión de como funciona. A partir de aquí es interesante conocer como funciona la configuración avanzada de red en la cual podemos, entre otras cosas, especificar un bridge de conexión para el contenedor, activar la comunicación entre contenedores, configuración de IPTABLES, DNS, IP, etc. Estos son algunos de los parámetros disponibles a la hora de arrancar el contenedor que establecen estas configuraciones:\n\nConfiguración de bridges:\n\n```\n--bridge=BRIDGE\n\n```\n\nActivar la comunicación entre contenedores:\n\n```\n--icc=true|false\n\n```\n\nEspecificar la IP a la que responderá el contenedor:\n\n```\n--ip=IP_ADDRESS\n\n```\n\nHabilitar IP Forwarding:\n\n```\n--ip-forward=true|false\n\n```\n\nHabilitar iptables:\n\n```\n--iptables=true|false\n\n```\n\nEspecificar DNS y dominio de búsqueda DNS:\n\n```\n--dns=IP_ADDRESS\n--dns-search=DOMAIN\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunocascio%2Fdocker-espanol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrunocascio%2Fdocker-espanol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrunocascio%2Fdocker-espanol/lists"}