# Usar contenedores para el desarrollo de Python


## Requisitos previos

Completa [Contenedorizar una aplicación Python](/guides/python/develop/containerize/).

## Descripción general

En esta sección, aprenderás a configurar un entorno de desarrollo para tu aplicación contenedorizada. Esto incluye:

- Agregar una base de datos local y persistir datos
- Configurar Compose para actualizar automáticamente tus servicios de Compose en ejecución a medida que editas y guardas tu código

## Obtener la aplicación de ejemplo

Necesitarás clonar un nuevo repositorio para obtener una aplicación de ejemplo que incluya la lógica para conectarse a la base de datos.

1. Ve al directorio donde deseas clonar el repositorio y ejecuta el siguiente comando.

   ```console
   $ git clone https://github.com/estebanx64/python-docker-dev-example
   ```

2. En el directorio del repositorio clonado, crea manualmente los recursos de Docker o ejecuta `docker init` para crearlos.

   **Usar Docker Init**



   En el directorio del repositorio clonado, ejecuta `docker init`. Consulta el siguiente ejemplo para responder a las preguntas de `docker init`.

   ```console
   $ docker init
   Welcome to the Docker Init CLI!

   This utility will walk you through creating the following files with sensible defaults for your project:
     - .dockerignore
     - Dockerfile
     - compose.yaml
     - README.Docker.md

   Let's get started!

   ? What application platform does your project use? Python
   ? What version of Python do you want to use? 3.12
   ? What port do you want your app to listen on? 8001
   ? What is the command to run your app? python3 -m uvicorn app:app --host=0.0.0.0 --port=8001
   ```

   Crea un archivo llamado `.gitignore` con el siguiente contenido.

   ```text {collapse=true,title=".gitignore"}
   # Archivos compilados en bytes / optimizados / DLL
   __pycache__/
   *.py[cod]
   *$py.class

   # Extensiones C
   *.so

   # Distribución / empaquetado
   .Python
   build/
   develop-eggs/
   dist/
   downloads/
   eggs/
   .eggs/
   lib/
   lib64/
   parts/
   sdist/
   var/
   wheels/
   share/python-wheels/
   *.egg-info/
   .installed.cfg
   *.egg
   MANIFEST

   # Informes de pruebas unitarias / cobertura
   htmlcov/
   .tox/
   .nox/
   .coverage
   .coverage.*
   .cache
   nosetests.xml
   coverage.xml
   *.cover
   *.py,cover
   .hypothesis/
   .pytest_cache/
   cover/

   # PEP 582; utilizado por ejemplo por github.com/David-OConnor/pyflow y github.com/pdm-project/pdm
   __pypackages__/

   # Entornos
   .env
   .venv
   env/
   venv/
   ENV/
   env.bak/
   venv.bak/
   ```

   **Crear recursos manualmente**



   Si no tienes instalado Docker Desktop o prefieres crear los recursos manualmente, puedes crear los siguientes archivos en el directorio de tu proyecto.

   Crea un archivo llamado `Dockerfile` con el siguiente contenido.

   ```dockerfile {collapse=true,title=Dockerfile}
   # syntax=docker/dockerfile:1

   # Se proporcionan comentarios a lo largo de este archivo para ayudarte a empezar.
   # Si necesitas más ayuda, visita la guía de referencia de Dockerfile en
   # https://docs-docker.esdocu.com/go/dockerfile-reference/

   # ¿Quieres ayudarnos a mejorar esta plantilla? Comparte tus comentarios aquí: https://forms.gle/ybq9Krt8jtBL3iCk7

   ARG PYTHON_VERSION=3.12
   FROM python:${PYTHON_VERSION}-slim

   # Evita que Python escriba archivos pyc.
   ENV PYTHONDONTWRITEBYTECODE=1

   # Evita que Python almacene en búfer stdout y stderr para evitar situaciones en las que
   # la aplicación se bloquee sin emitir ningún registro debido al almacenamiento en búfer.
   ENV PYTHONUNBUFFERED=1

   WORKDIR /app

   # Crea un usuario no privilegiado bajo el cual se ejecutará la aplicación.
   # Consulta https://docs-docker.esdocu.com/go/dockerfile-user-best-practices/
   ARG UID=10001
   RUN adduser \
       --disabled-password \
       --gecos "" \
       --home "/nonexistent" \
       --shell "/sbin/nologin" \
       --no-create-home \
       --uid "${UID}" \
       appuser

   # Descarga las dependencias como un paso separado para aprovechar la caché de Docker.
   # Aprovecha un montaje de caché en /root/.cache/pip para acelerar las compilaciones posteriores.
   # Aprovecha un montaje de tipo bind en requirements.txt para evitar tener que copiarlos
   # en esta capa.
   RUN --mount=type=cache,target=/root/.cache/pip \
       --mount=type=bind,source=requirements.txt,target=requirements.txt \
       python -m pip install -r requirements.txt

   # Cambia al usuario no privilegiado para ejecutar la aplicación.
   USER appuser

   # Copia el código fuente en el contenedor.
   COPY . .

   # Expone el puerto en el que escucha la aplicación.
   EXPOSE 8001

   # Ejecuta la aplicación.
   CMD ["python3", "-m", "uvicorn", "app:app", "--host=0.0.0.0", "--port=8001"]
   ```

   Crea un archivo llamado `compose.yaml` con el siguiente contenido.

   ```yaml {collapse=true,title=compose.yaml}
   # Se proporcionan comentarios a lo largo de este archivo para ayudarte a empezar.
   # Si necesitas más ayuda, visita la guía de referencia de Docker Compose en
   # https://docs-docker.esdocu.com/go/compose-spec-reference/

   # Aquí las instrucciones definen tu aplicación como un servicio llamado "server".
   # Este servicio se construye a partir del Dockerfile en el directorio actual.
   # Puedes agregar otros servicios de los que pueda depender tu aplicación aquí, como una
   # base de datos o una caché. Para ver ejemplos, consulta el repositorio Awesome Compose:
   # https://github.com/docker/awesome-compose
   services:
     server:
       build:
         context: .
       ports:
         - 8001:8001
   # La sección comentada a continuación es un ejemplo de cómo definir una base de datos PostgreSQL
   # que tu aplicación puede utilizar. `depends_on` le indica a Docker Compose que
   # inicie la base de datos antes de tu aplicación. El volumen `db-data` persiste los
   # datos de la base de datos entre los reinicios del contenedor. El secreto `db-password` se utiliza
   # para establecer la contraseña de la base de datos. Debes crear `db/password.txt` y agregar
   # la contraseña que elijas antes de ejecutar `docker compose up`.
   #     depends_on:
   #       db:
   #         condition: service_healthy
   #   db:
   #     image: postgres:18
   #     restart: always
   #     user: postgres
   #     secrets:
   #       - db-password
   #     volumes:
   #       - db-data:/var/lib/postgresql
   #     environment:
   #       - POSTGRES_DB=example
   #       - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
   #     expose:
   #       - 5432
   #     healthcheck:
   #       test: [ "CMD", "pg_isready" ]
   #       interval: 10s
   #       timeout: 5s
   #       retries: 5
   # volumes:
   #   db-data:
   # secrets:
   #   db-password:
   #     file: db/password.txt
   ```

   Crea un archivo llamado `.dockerignore` con el siguiente contenido.

   ```text {collapse=true,title=".dockerignore"}
   # Incluye aquí cualquier archivo o directorio que no quieras que se copie a tu
   # contenedor (por ejemplo, artefactos de compilación local, archivos temporales, etc.).
   #
   # Para obtener más ayuda, consulta la guía de referencia del archivo .dockerignore en
   # https://docs-docker.esdocu.com/go/build-context-dockerignore/

   **/.DS_Store
   **/__pycache__
   **/.venv
   **/.classpath
   **/.dockerignore
   **/.env
   **/.git
   **/.gitignore
   **/.project
   **/.settings
   **/.toolstarget
   **/.vs
   **/.vscode
   **/*.*proj.user
   **/*.dbmdl
   **/*.jfm
   **/bin
   **/charts
   **/docker-compose*
   **/compose.y*ml
   **/Dockerfile*
   **/node_modules
   **/npm-debug.log
   **/obj
   **/secrets.dev.yaml
   **/values.dev.yaml
   LICENSE
   README.md
   ```

   Crea un archivo llamado `.gitignore` con el siguiente contenido.

   ```text {collapse=true,title=".gitignore"}
   # Archivos compilados en bytes / optimizados / DLL
   __pycache__/
   *.py[cod]
   *$py.class

   # Extensiones C
   *.so

   # Distribución / empaquetado
   .Python
   build/
   develop-eggs/
   dist/
   downloads/
   eggs/
   .eggs/
   lib/
   lib64/
   parts/
   sdist/
   var/
   wheels/
   share/python-wheels/
   *.egg-info/
   .installed.cfg
   *.egg
   MANIFEST

   # Informes de pruebas unitarias / cobertura
   htmlcov/
   .tox/
   .nox/
   .coverage
   .coverage.*
   .cache
   nosetests.xml
   coverage.xml
   *.cover
   *.py,cover
   .hypothesis/
   .pytest_cache/
   cover/

   # PEP 582; utilizado por ejemplo por github.com/David-OConnor/pyflow y github.com/pdm-project/pdm
   __pypackages__/

   # Entornos
   .env
   .venv
   env/
   venv/
   ENV/
   env.bak/
   venv.bak/
   ```

   

## Agregar una base de datos local y persistir datos

Puedes usar contenedores para configurar servicios locales, como una base de datos. En esta sección, actualizarás el archivo `compose.yaml` para definir un servicio de base de datos y un volumen para persistir los datos.

En el directorio del repositorio clonado, abre el archivo `compose.yaml` en un IDE o editor de texto. `docker init` se encargó de crear la mayoría de las instrucciones, pero tendrás que actualizarlo para tu aplicación específica.

En el archivo `compose.yaml`, debes descomentar todas las instrucciones de la base de datos. Además, debes agregar el archivo de contraseña de la base de datos como una variable de entorno al servicio server y especificar el archivo de secretos que se utilizará.

El siguiente es el archivo `compose.yaml` actualizado.

```yaml {hl_lines="7-43"}
services:
  server:
    build:
      context: .
    ports:
      - 8001:8001
    environment:
      - POSTGRES_SERVER=db
      - POSTGRES_USER=postgres
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
  db:
    image: postgres:18
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt
```

> [!NOTE]
>
> Para obtener más información sobre las instrucciones en el archivo Compose, consulta la [referencia del archivo Compose](/reference/compose-file/).

Antes de ejecutar la aplicación usando Compose, observa que este archivo Compose especifica un archivo `password.txt` para contener la contraseña de la base de datos. Debes crear este archivo, ya que no está incluido en el repositorio fuente.

En el directorio del repositorio clonado, crea un nuevo directorio llamado `db` y dentro de ese directorio crea un archivo llamado `password.txt` que contenga la contraseña para la base de datos. Usando tu IDE o editor de texto favorito, agrega el siguiente contenido al archivo `password.txt`.

```text
mysecretpassword
```

Guarda y cierra el archivo `password.txt`.

Ahora deberías tener el siguiente contenido en tu directorio `python-docker-dev-example`.

```text
├── python-docker-dev-example/
│   ├── db/
│   │   └── password.txt
│   ├── app.py
│   ├── config.py
│   ├── requirements.txt
│   ├── .dockerignore
│   ├── .gitignore
│   ├── compose.yaml
│   ├── Dockerfile
│   ├── README.Docker.md
│   └── README.md
```

Ahora, ejecuta el siguiente comando `docker compose up` para iniciar tu aplicación.

```console
$ docker compose up --build
```

Ahora prueba tu punto de conexión de la API. Abre una nueva terminal y realiza una solicitud al servidor utilizando los comandos curl:

Vamos a crear un objeto con un método POST:

```console
$ curl -X 'POST' \
  'http://localhost:8001/heroes/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "name": "my hero",
  "secret_name": "austing",
  "age": 12
}'
```

Deberías recibir la siguiente respuesta:

```json
{
  "age": 12,
  "id": 1,
  "name": "my hero",
  "secret_name": "austing"
}
```

Hagamos una solicitud GET con el siguiente comando curl:

```console
$ curl -X 'GET' \
  'http://localhost:8001/heroes/' \
  -H 'accept: application/json'
```

Deberías recibir la misma respuesta que arriba porque es el único objeto que tenemos en la base de datos.

```json
{
  "age": 12,
  "id": 1,
  "name": "my hero",
  "secret_name": "austing"
}
```

Presiona `ctrl+c` en la terminal para detener tu aplicación.

## Actualizar servicios automáticamente

Utiliza Compose Watch para actualizar automáticamente tus servicios de Compose en ejecución a medida que editas y guardas tu código. Para obtener más detalles sobre Compose Watch, consulta [Usar Compose Watch](/compose/how-tos/file-watch/).

Abre tu archivo `compose.yaml` en un IDE o editor de texto y luego agrega las instrucciones de Compose Watch. El siguiente es el archivo `compose.yaml` actualizado.

```yaml {hl_lines="17-20"}
services:
  server:
    build:
      context: .
    ports:
      - 8001:8001
    environment:
      - POSTGRES_SERVER=db
      - POSTGRES_USER=postgres
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - db-password
    develop:
      watch:
        - action: rebuild
          path: .
  db:
    image: postgres:18
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt
```

Ejecuta el siguiente comando para ejecutar tu aplicación con Compose Watch.

```console
$ docker compose watch
```

En una terminal, haz un curl a la aplicación para obtener una response.

```console
$ curl http://localhost:8001
Hello, Docker!
```

Cualquier cambio en los archivos fuente de la aplicación en tu máquina local se reflejará inmediatamente en el contenedor en ejecución.

Abre `python-docker-dev-example/app.py` en un IDE o editor de texto y actualiza la cadena `Hello, Docker!` agregando algunos signos de exclamación más.

```diff
-    return 'Hello, Docker!'
+    return 'Hello, Docker!!!'
```

Guarda los cambios en `app.py` y luego espera unos segundos a que la aplicación se vuelva a compilar. Haz un curl a la aplicación nuevamente y verifica que aparezca el texto actualizado.

```console
$ curl http://localhost:8001
Hello, Docker!!!
```

Presiona `ctrl+c` en la terminal para detener tu aplicación.

## Resumen

En esta sección, viste cómo configurar tu archivo Compose para agregar una base de datos local y persistir datos. También aprendiste a usar Compose Watch para compilar y ejecutar automáticamente tu contenedor cuando actualizas tu código.

Información relacionada:

- [Referencia del archivo Compose](/reference/compose-file/)
- [Compose file watch](/compose/how-tos/file-watch/)
- [Compilaciones de múltiples etapas (multi-stage builds)](/build/building/multi-stage/)

## Pasos siguientes

En la siguiente sección, aprenderás cómo puedes configurar el análisis de código (linting), el formateo y la comprobación de tipos para seguir las mejores prácticas en aplicaciones Python.

