61

Cómo crear tu contenedor de Docker para tu aplicación en Django

113229Puntos

hace 6 años

Docker resulta útil a la hora de crear un entorno de desarrollo ya que empaqueta la aplicación en contenedores con lo necesario para que pueda funcionar sin importar el sistema operativo que corre. Incluso si no tienes dependencias o servicios, como Postgres instalados en tu máquina.

Este tutorial quiero enseñarte cómo puedes tener tu primer contenedor con una aplicación de Django. Ojo, estoy suponiendo que tienes Pipenv, Docker y Docker Compose.

Dockerfile

7Ha8pRa.png

Lo primero que debemos hacer es crear un proyecto con Django con su entorno virtual.

mkdir PlatziDocker
pipenv install --three django
pipenv run django-admin startproject config .

Listo. Si Pipenv no te resulta familiar, escribí un tutorial acerca de esta herramienta hace un par de semanas, te puede ahorra tiempo a la hora de usar entornos virtuales.

Sigamos, ahora es necesario crear un Dockerfile. Así, sin extensión ni nada parecido.

Nuestro Dockerfile se debe ver así.

FROM python:3.6ENV PYTHONUNBUFFERED 1COPY. /code/
WORKDIR/code/
RUNpip install pipenv
RUNpipenv install --system
EXPOSE8000

La primera línea FROM python:3.6 le dice a Docker que vamos a usar esa imagen como base para construir la nuestra. Es un contenedor de Ubuntu que tiene instalado la versión 3.6 de Python.

La segunda línea ENV PYTHONUNBUFFERED 1 es una variable de entorno diciéndole a Docker que nos muestre el Standart Output(salida) y Standart Error(errores) en la terminal como estamos acostumbrados. De esta misma manera podemos declarar un ENV SECRET_KEY='__KEY__' para tener nuestra llave secreta alejada de nuestro archivo settings.

La tercera línea COPY . /code/ es un comando para copiar todos los archivos y carpetas que se encuentran al mismo nivel (.) del Dockerfile a una carpeta llamada code. Luego en WORKDIR /code/ le decimos que nuestra área de trabajo va a ser esa carpeta, todos los comandos que corramos, se ejecutarán dentro de la misma.

Las siguientes dos líneas RUN pip install pipenv y RUN pipenv install --system le dirán a docker que ejecute esos comandos, primero instalará Pipenv con pip para luego ejecutar la siguiente e instalar las dependencias de nuestro archivo Pipfile.lock y como le estamos pasando como argumento --system no se va a crear un entorno virtual, no lo necesitamos.

Y la última línea EXPOSE 8000, para correr bien runserver nuestro contenedor necesita acceder al puerto 8000.

Listo, con eso ya tenemos nuestro Dockerfile. Ahora solo necesitamos hacer docker build . y vamos a ver una larga lista de cosas en la terminal, diferentes pasos. Todo debería ir bien.

Docker Compose

Ahora, Docker Compose nos ayuda a correr más de un contenedor en nuestra aplicación de Docker, por ejemplo si queremos tener una base de datos como Postgres o un servicio de caché como Redis o un servidor como Nginx. Para esto necesitamos un archivo en el mismo nivel que nuestro archivo Dockerfile y lo llamamos docker-compose.yml, este si tiene una extensión. Es un archivo YAML.

Tendría esto:

versiónversion: '3'

services:	db:    	image: postgres:10.1    	volumes:
        	- postgres_data:/var/lib/postgresql/data
	web:    	build: .
    	command: python /code/manage.py migrate --noinput
    	command: python /code/manage.py runserver 0.0.0.0:8000    	volumes:
        	- .:/code
    	ports:
        	- "8000:8000"    	depends_on:
        	- db
    	environment:
        	- DJANGO_SETTINGS_MODULE=config.settings
        	- SECRET_KEY=${SECRET_KEY}
volumes:postgres_data:

Ahora vamos a explicarlo línea por línea.

La primera version: '3' define la versión de Compose que queremos usar.

Ahora en services vamos a definir, de manera identada, los servicios que queremos corran en diferentes contenedores.

Con el primer servicio le decimos a docker que queremos la imagen de PostgreSQL. Ni siquiera necesitamos tenerlo instalado en nuestra sistema, Docker la descargará desde el Docker Hub con la versión que le especificamos. Y con volumes le decimos en que parte de nuestro contenedor queremos que se guarden los datos, en este caso /var/lib/postgresql/data. Si recuerdan, les dije que nuestra imagen de Python tiene como base una imagen de Ubuntu, eso hace que tenga varios subdirectorios. Un volumen permite que nuestros datos persistan sin importar el ciclo de vida del contenedor

Por supuesto, para esto tenemos que decirle a Django que vamos a usar Postgres como base de datos, así que tenemos que deshacernos de ese sqlite3. Colocaremos esto:

DATABASES = {
	'default': {
    	'ENGINE': 'django.db.backends.postgresql_psycopg2',
    	'NAME': 'postgres',
    	'USER': 'postgres',
    	'PASSWORD': 'postgres',
    	'HOST': 'db',
    	'PORT': '5432',
	}
}

El siguiente servicio es el web y le decimos que construya build . nuestra imagen desde el directorio actual. Los siguientes dos commandos command, uno ejecutará las migraciones sin mostrar la salida en nuestra consola --no-input y el segundo levantará el servidor cuando corramos el contenedor.

volumes al igual que con la base de datos, le dira a nuestro contenedor donde estará nuestro código ./code. Justo después con ports mapeamos nuestro puerto 8000 al puerto 8000 del contenedor de Docker, haciendo que nuestro servidor sea accesible vía localhost:8000 o 0.0.0.0:8000. Ahora depends_on le dice que nuestro servicio web necesita esperar por la base de datos, Compose primero levantará ese servicio antes de pasar a web.

Con environment, puedes adivinar que son variables de entorno. Si es el caso de que estamos trabajando con diferentes archivos settings. Puedes colocar ahí, la variable DJANGO_SETTINGS_MODULE para especificar con cuál archivo quieres trabajar, puede ser local, production o test. La siguiente SECRET_KEY es una manera de llamar a una variable de entorno colocada en un archivo .env

Ya que Pipenv carga automáticamente las variables de un archivo .env podemos usar ese mismo archivo para mandarle la variable de entorno a nuestro contenedor con la línea SECRET_KEY=${SECRET_KEY}

Y nuestras últimas dos líneas, Compose tiene una regla donde tenemos que listar nuestros volúmenes en una llave volumes al mismo nivel de identación que services o version

Con el archivo guardado. Podemos ejecutar ahora docker-compose up también nos mostrará cierta cantidad de información en la terminal pero al final deberías tener un servidor corriendo en localhost:8000 o 0.0.0.0:8000 con la acostumbrada salida de Django.

KLB7fnp.png

Conclusión

De esta manera, puedes olvidarte de los entornos virtuales y de tener algunos programas instalados en tu máquina local, puedes tener tu aplicación empaquetada de esta manera y usarla en cualquier SO mientras tengas Docker instalado. Para saber más acerca de Docker te invito a ver los dos grandes cursos que tiene Platzi, aprenderás esto y más.

Kevin
Kevin
iKenshu

113229Puntos

hace 6 años

Todas sus entradas
Escribe tu comentario
+ 2
Ordenar por:
2

Demasiados errores y omisiones, es imposible realizar con éxito este ejemplo siguiendo las indicaciones, ya que como indicó tienen errores.
Creo que deben mejorar en el control de calidad de lo que se publica, ya que afecta negativamente el esfuerzo por aprender nuevas técnologías de software.

2
17230Puntos
3 años

Buen tutorial y una buena forma de unir dos cursos dentro de la plataforma, llego aquí 3 años después y aun es relevante el contenido

1
18668Puntos
3 años

Hola comunidad en Platzi que utiliza Django.

Tengo una pregunta muy importante en mis proyectos de Django con Docker, ya que seguido tengo este error y no entiendo porqué.
La razón es por que aveces me aparece y aveces no.

docker-compose up
Starting premiosplatziapp_db_1 ... done
Starting premiosplatziapp_web_1 ... done
Attaching to premiosplatziapp_db_1, premiosplatziapp_web_1
db_1   |
db_1   | PostgreSQL Database directory appears to contain a database; Skipping initialization
db_1   |
db_1   | 2022-02-20 04:36:28.055 UTC [1] LOG:  starting PostgreSQL 14.2 (Debian 14.2-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
db_1   | 2022-02-20 04:36:28.056 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db_1   | 2022-02-20 04:36:28.057 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db_1   | 2022-02-20 04:36:28.064 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1   | 2022-02-20 04:36:28.082 UTC [30] LOG:  database system was interrupted; last known up at 2022-02-20 04:34:04 UTC
web_1  | Watching forfile changes with StatReloader
web_1  | Performing system checks...
web_1  |
web_1  | System check identified no issues (0 silenced).
db_1   | 2022-02-20 04:36:31.093 UTC [31] FATAL:  the database system is starting up
web_1  | Exception in thread django-main-thread:
web_1  | Traceback (most recent call last):
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 230, in ensure_connection
web_1  |     self.connect()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 211, in connect
web_1  |     self.connection = self.get_new_connection(conn_params)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/postgresql/base.py", line 199, in get_new_connection
web_1  |     connection = Database.connect(**conn_params)
web_1  |   File"/usr/local/lib/python3.8/site-packages/psycopg2/__init__.py", line 122, in connect
web_1  |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
web_1  | psycopg2.OperationalError: connection to server at "db" (172.29.0.2), port 5432 failed: FATAL:  the database system is starting up
web_1  |
web_1  |
web_1  | The above exception was the direct cause of the following exception:
web_1  |
web_1  | Traceback (most recent call last):
web_1  |   File"/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner
web_1  |     self.run()
web_1  |   File"/usr/local/lib/python3.8/threading.py", line 870, inrun
web_1  |     self._target(*self._args, **self._kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/autoreload.py", line 64, in wrapper
web_1  |     fn(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/core/management/commands/runserver.py", line 127, in inner_run
web_1  |     self.check_migrations()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 505, in check_migrations
web_1  |     executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/migrations/executor.py", line 18, in __init__
web_1  |     self.loader = MigrationLoader(self.connection)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/migrations/loader.py", line 53, in __init__
web_1  |     self.build_graph()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/migrations/loader.py", line 223, in build_graph
web_1  |     self.applied_migrations = recorder.applied_migrations()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/migrations/recorder.py", line 77, in applied_migrations
web_1  |     if self.has_table():
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/migrations/recorder.py", line 55, in has_table
web_1  |     with self.connection.cursor() as cursor:
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 270, in cursor
web_1  |     return self._cursor()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 246, in _cursor
web_1  |     self.ensure_connection()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 230, in ensure_connection
web_1  |     self.connect()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__
web_1  |     raise dj_exc_value.with_traceback(traceback) from exc_value
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 230, in ensure_connection
web_1  |     self.connect()
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/base/base.py", line 211, in connect
web_1  |     self.connection = self.get_new_connection(conn_params)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 25, in inner
web_1  |     return func(*args, **kwargs)
web_1  |   File"/usr/local/lib/python3.8/site-packages/django/db/backends/postgresql/base.py", line 199, in get_new_connection
web_1  |     connection = Database.connect(**conn_params)
web_1  |   File"/usr/local/lib/python3.8/site-packages/psycopg2/__init__.py", line 122, in connect
web_1  |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
web_1  | django.db.utils.OperationalError: connection to server at "db" (172.29.0.2), port 5432 failed: FATAL:  the database system is starting up
web_1  |
db_1   | 2022-02-20 04:36:31.291 UTC [30] LOG:  database system was not properly shut down; automatic recovery in progress
db_1   | 2022-02-20 04:36:31.337 UTC [30] LOG:  redo starts at 0/1869138
db_1   | 2022-02-20 04:36:31.337 UTC [30] LOG:  invalid record length at 0/1869170: wanted 24, got 0
db_1   | 2022-02-20 04:36:31.337 UTC [30] LOG:  redo done at 0/1869138 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
db_1   | 2022-02-20 04:36:31.372 UTC [1] LOG:  database system is ready to accept connections
^CGracefully stopping... (press Ctrl+C again to force)
Killing premiosplatziapp_web_1  ... done
Killing premiosplatziapp_db_1   ... done
ERROR: 2
MacBook@MacBook-Pro ~/D/P/P/premiosplatziapp (deployment) [2]> docker-compose build

Docker compose

version:"3.9"   
services:  db:    image: postgres
    volumes:
      - ./data/db:/var/lib/postgresql/data
    environment:
      - POSTGRES_NAME=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
  web:    build: .
    command: python manage.py runserver 0.0.0.0:8000    volumes:
      - .:/code
    ports:
      - "8000:8000"    depends_on:
      - db

Dockerfile

FROM python:3.8ENV PYTHONUNBUFFERED=1WORKDIR/code
COPYrequirements.txt /code/
RUNpip install -r requirements.txt
COPY. /code/

Pienso que el error se debe a que no encuentra la Base de Datos en ocasiones, es por eso que lo tengo que detener y volver a hacer rebuild cada rato hasta que funciona.

Gracias.

Ejecutado:

MacBook@MacBook-Pro ~/D/P/P/premiosplatziapp (deployment)> docker-compose up
Starting premiosplatziapp_db_1 ... done
Recreating premiosplatziapp_web_1 ... done
Attaching to premiosplatziapp_db_1, premiosplatziapp_web_1
db_1   |
db_1   | PostgreSQL Database directory appears to contain a database; Skipping initialization
db_1   |
db_1   | 2022-02-2004:37:24.551 UTC [1] LOG:  starting PostgreSQL 14.2 (Debian 14.2-1.pgdg110+1) onx86_64-pc-linux-gnu, compiledbygcc (Debian10.2.1-6) 10.2.120210110, 64-bit
db_1   | 2022-02-2004:37:24.551 UTC [1] LOG:  listening onIPv4address"0.0.0.0", port5432
db_1   | 2022-02-2004:37:24.551 UTC [1] LOG:  listening onIPv6address"::", port5432
db_1   | 2022-02-2004:37:24.568 UTC [1] LOG:  listening onUnixsocket"/var/run/postgresql/.s.PGSQL.5432"
db_1   | 2022-02-2004:37:24.597 UTC [27] LOG:  database system was interrupted; last known up at2022-02-2004:36:31 UTC
db_1   | 2022-02-2004:37:28.493 UTC [27] LOG:  database system was not properly shut down; automatic recovery in progress
db_1   | 2022-02-2004:37:28.501 UTC [27] LOG:  redo starts at0/18691E8
db_1   | 2022-02-2004:37:28.501 UTC [27] LOG:  invalid record lengthat0/1869220: wanted 24, got 0
db_1   | 2022-02-2004:37:28.501 UTC [27] LOG:  redo done at0/18691E8system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
db_1   | 2022-02-2004:37:28.561 UTC [1] LOG:  database system is ready to accept connections
web_1  | Watching forfile changes with StatReloader
web_1  | Performing system checks...
web_1  |
web_1  | System check identified no issues (0 silenced).
web_1  | February 19, 2022 - 22:37:28
web_1  | Django version4.0, using settings 'premiosplatziapp.settings'
web_1  | Starting development server athttp://0.0.0.0:8000/
web_1  | Quit the server with CONTROL-C.
web_1  | [19/Feb/202222:37:46] "GET / HTTP/1.1"2001336