Tu primer Dockerfile desde cero: paso a paso

Docker & Containers · Por · Publicado el · Actualizado el · 8 min de lectura

Manos escribiendo en un portátil con código abierto en el editor visible
Ilustra el flujo práctico: editar, construir y probar tu Dockerfile en el portátil Foto: Lukas Blazek / Pexels

Esta guía muestra, de forma práctica, cómo crear tu primer Dockerfile desde cero, construir la imagen y ejecutar un contenedor con una pequeña aplicación de ejemplo. Entenderás las directivas esenciales, cómo funciona el caché de capas, .dockerignore y un ajuste básico de seguridad. Sigue los pasos numerados y valida todo al final.

Prerequisitos y visión rápida de lo que harás

Necesitarás:

- Docker instalado y en ejecución. En Linux, tu usuario debe poder usar Docker sin sudo.

- Editor de texto y terminal.

- Algo de espacio en disco para las imágenes.

Vamos a crear una app simple, escribir el Dockerfile, hacer el build, ejecutar y optimizar con .dockerignore y un usuario no-root. Si quieres comparar cuándo usar contenedores o máquinas virtuales, consulta la comparativa Docker vs máquinas virtuales para HA.

  • Docker instalado (Desktop, Engine o similar)
  • Terminal con acceso a internet
  • Editor de texto para crear los archivos del proyecto
# Verifique se o Docker está funcionando
docker --version
# (Opcional) Teste rápido do daemon
docker run --rm hello-world

Estructura básica de un Dockerfile

Un Dockerfile describe cómo montar una imagen a partir de capas. Cada instrucción crea o influye en una capa en caché. A continuación, las directivas más usadas y un ejemplo mínimo. Usa imágenes base ligeras (p. ej.: variantes slim) para reducir el tamaño.

Directiva Para qué sirve Ejemplo
FROM Imagen base inicial FROM python:3.12-slim
WORKDIR Define el directorio de trabajo WORKDIR /app
COPY Copia archivos a la imagen COPY . .
RUN Ejecuta comandos de build RUN pip install -r requirements.txt
EXPOSE Documenta el puerto usado EXPOSE 8000
CMD Comando predeterminado al iniciar CMD ["python", "app.py"]
# Exemplo mínimo
FROM alpine:3.20
CMD ["echo", "Hello from Docker!"]

Prepara el proyecto de ejemplo (Flask)

Crearemos una API mínima en Flask para validar red y logs. En Windows, si prefieres, crea los archivos manualmente en lugar de usar heredoc. Los archivos serán: requirements.txt y app.py.

mkdir hello-docker && cd hello-docker

# requirements.txt
cat > requirements.txt <<'REQ'
flask
REQ

# app.py
cat > app.py <<'PY'
from flask import Flask
app = Flask(__name__)

@app.get("/")
def index():
    return {"message": "Hello from Docker"}

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
PY

Escribe el Dockerfile paso a paso

Crea un archivo llamado Dockerfile en la raíz del proyecto. Primero copiamos dependencias para aprovechar el caché y luego el código. Expón la puerta del app y define el comando de inicio.

# Dockerfile (versão inicial)
FROM python:3.12-slim

# Impede criação de .pyc e ativa logs imediatos no stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# Define diretório de trabalho
WORKDIR /app

# Copia dependências primeiro para aproveitar cache
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copia o restante do código
COPY . .

# Documenta a porta usada
EXPOSE 8000

# Comando padrão
CMD ["python", "app.py"]

Haz el build y entiende el caché de capas

El build crea la imagen ejecutando las instrucciones del Dockerfile. Los cambios en archivos copiados antes invalidan el caché de las etapas siguientes. Modificar solo el código, manteniendo requirements.txt igual, reutiliza el caché de dependencias. En CI/CD, automatiza estos comandos (consulta la guía de CI/CD con GitHub Actions).

# Execute a partir da pasta do projeto (onde está o Dockerfile)
docker build -t hello-docker:dev .

# Verifique a imagem criada
docker image ls | grep hello-docker

# Rode novamente para observar o uso de cache
docker build -t hello-docker:dev .

# (Opcional) Inspecione o histórico de camadas
docker image history hello-docker:dev

Ejecuta el container y mapea el puerto

Ahora vamos a levantar el contenedor y mapear el puerto 8000 del host al 8000 del contenedor. Usa -d para ejecutarlo en background. Para detenerlo, usa docker stop. Mapear puertos permite acceder a la aplicación desde el navegador.

# Executa em foreground (Ctrl+C para parar)
docker run --rm -p 8000:8000 --name hello hello-docker:dev

# Ou em background (daemon)
# docker run -d --rm -p 8000:8000 --name hello hello-docker:dev

# Em outro terminal, teste:
# curl http://localhost:8000

Optimiza: .dockerignore y usuario no-root

Reduce el contexto de build y mejora la seguridad. El .dockerignore evita enviar archivos innecesarios al daemon. Ejecutar como no-root reduce el impacto de fallos. Si tu imagen expone una API a Internet, consulta prácticas de seguridad para APIs: OAuth2 y rate limiting.

# .dockerignore (crie este arquivo na raiz)
__pycache__/
*.pyc
.env
.git
.gitignore
README*

# Dockerfile (otimizado)
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1
WORKDIR /app

# Camada de dependências (cacheável)
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copia o código
COPY . .

# Cria usuário não-root e ajusta permissões
RUN adduser --disabled-password --gecos "" appuser \
    && chown -R appuser:appuser /app
USER appuser

EXPOSE 8000
CMD ["python", "app.py"]

Cómo confirmar que funcionó y resolver problemas

Verifica la aplicación, los logs y el estado del contenedor. En caso de error, revisa los mensajes y los puertos. Para estandarizar logs de tu app, considera adoptar logs estructurados; consulta la guía de logs en JSON y correlación.

  • No abre en el navegador: confirma que mapeaste -p 8000:8000 y accede a http://localhost:8000.
  • Puerto en uso: cambia a -p 8080:8000 y prueba de nuevo.
  • Permiso denegado al usar USER: asegúrate de chown -R appuser:appuser /app en el Dockerfile.
  • Dependencia no encontrada: actualiza requirements.txt y reconstruye sin caché (usa --no-cache).
  • Daemon inaccesible: inicia el servicio Docker/Desktop y verifica permisos del usuario (en Linux, grupo docker).
  • Imagen muy grande: usa una base slim, .dockerignore y limpia caches de build cuando sea posible.
# Teste HTTP e logs
curl -s http://localhost:8000 | jq .

docker ps --filter name=hello

docker logs -f hello

# (Opcional) Acesse o shell do container para inspeção
# docker exec -it hello sh

Preguntas frecuentes

¿Qué es un Dockerfile y por qué lo necesito?

Un Dockerfile es un archivo de texto con instrucciones para construir una imagen de contenedor a partir de capas. Garantiza builds reproducibles, estandariza el entorno y facilita distribuir y ejecutar tu aplicación en cualquier host con Docker. Sin Dockerfile dependerías de configuraciones manuales menos predecibles y con más riesgo de diferencias entre entornos.

¿Necesito tener Python instalado localmente para seguir el tutorial?

No, no es necesario instalar Python en tu máquina para seguir este tutorial: el runtime de Python se proporciona desde la imagen base durante el build y la ejecución del contenedor. Sí necesitas Docker instalado y en ejecución (y permisos adecuados en Linux). Si usas otro runtime adapta la instrucción FROM, los puertos y el comando de inicio al stack elegido.

¿Cuál es la diferencia entre CMD y ENTRYPOINT?

CMD establece el comando por defecto que se ejecuta al iniciar el contenedor y puede ser sobrescrito en la línea de comandos. ENTRYPOINT fija el ejecutable principal del contenedor; los argumentos adicionales pueden venir de CMD. Usa ENTRYPOINT cuando quieras que el proceso principal sea fijo y CMD para proveer argumentos por defecto que se puedan modificar.

¿Es obligatorio usar .dockerignore?

No es obligatorio, pero es muy recomendable. .dockerignore reduce el contexto de build enviado al daemon, acelera el proceso de construcción y evita que archivos innecesarios o sensibles (caches, binarios de build, directorios VCS, secretos) se incluyan en la imagen. Mantenerlo limpio ayuda a imágenes más pequeñas y builds más rápidos.

¿Cómo reducir el tiempo de build y aprovechar el caché?

Para aprovechar el caché copia e instala primero las dependencias (p. ej. requirements.txt) y luego añade el código fuente; así, si sólo cambias el código se reutilizan las capas de dependencias. Evita RUN innecesarios, combina comandos cuando tenga sentido y elige una imagen base ligera (slim). Si necesitas un rebuild limpio usa --no-cache en el build.

¿Cómo publicar la imagen en un registry?

El flujo básico es: elige un registry, haz login con la CLI correspondiente, etiqueta la imagen con el namespace del registry y realiza el push. En pipelines de CI/CD automatiza login, build, tag y push para entornos reproducibles. Revisa las políticas de acceso y nombres consistentes por entorno antes de publicar.

¿Listo para el siguiente paso?

Automatiza builds y despliegues con pipelines. Empieza con GitHub Actions para crear, testear y publicar imágenes en cada push.

Leer guía de CI/CD

Ver también