Dockerfile y Builds Personalizados

Por: Artiko
dokploydockerfilebuildsdocker

Dockerfile y Builds Personalizados

Cuando Nixpacks no es suficiente o necesitas control total sobre la imagen final, Dokploy permite usar tu propio Dockerfile. Es el metodo ideal para proyectos con dependencias complejas, imagenes minimas o procesos de build no convencionales.

Cuando usar Dockerfile custom

Opta por un Dockerfile propio si:

Configurar build type como Docker en Dokploy

  1. Ve a tu proyecto y selecciona el servicio (o crea uno nuevo)
  2. En la seccion General, busca Build Type
  3. Cambia de Nixpacks a Dockerfile
  4. En Dockerfile Path, indica la ruta al archivo (por defecto ./Dockerfile)
  5. Clic en Save y luego Deploy

Dokploy ejecutara docker build usando el Dockerfile especificado y desplegara la imagen resultante.

Ejemplo: Dockerfile multi-stage para app Go

Una API REST en Go con multi-stage build que produce una imagen final de ~15MB:

# Stage 1: Build
FROM golang:1.23-alpine AS builder

WORKDIR /app

# Copiar dependencias primero para cache
COPY go.mod go.sum ./
RUN go mod download

# Copiar codigo fuente
COPY . .

# Compilar binario estatico
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/api

# Stage 2: Runtime
FROM alpine:3.21

RUN apk add --no-cache ca-certificates tzdata

WORKDIR /app
COPY --from=builder /app/server .

EXPOSE 8080

USER nobody:nobody

ENTRYPOINT ["./server"]

Por que multi-stage

Build arguments

Los build arguments permiten parametrizar el Dockerfile sin hardcodear valores.

Definir ARGs en el Dockerfile

FROM node:22-alpine AS builder

ARG NODE_ENV=production
ARG API_URL

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

COPY . .
RUN API_URL=${API_URL} npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

Configurar build args en Dokploy

  1. En la configuracion del servicio, ve a la seccion Advanced
  2. Busca Build Arguments
  3. Agrega cada argumento como clave-valor:
API_URL=https://api.midominio.com
NODE_ENV=production

Los build args solo estan disponibles durante el build, no en runtime. Para variables de runtime, usa la seccion Environment.

Build context

El build context define que directorio se envia al daemon de Docker para el build.

Proyecto en la raiz

Si el Dockerfile esta en la raiz del repositorio, no necesitas configurar nada:

mi-proyecto/
  Dockerfile
  src/
  package.json

Monorepo con multiples servicios

En un monorepo, configura el Build Path para apuntar al subdirectorio correcto:

monorepo/
  apps/
    api/
      Dockerfile
      src/
    web/
      Dockerfile
      src/
  packages/
    shared/

Para deployar api:

Si el Dockerfile necesita acceso a archivos fuera de su directorio (por ejemplo, packages/shared), mantiene el build path en la raiz y usa una ruta de Dockerfile:

Dentro del Dockerfile, las rutas COPY son relativas al build context:

FROM node:22-alpine
WORKDIR /app
COPY apps/api/package.json ./
COPY packages/shared ./packages/shared

Docker path personalizado

No estas limitado a ./Dockerfile. Dokploy permite especificar cualquier ruta:

./docker/production.Dockerfile
./infra/Dockerfile.api
./apps/web/Dockerfile.prod

Configura el campo Dockerfile Path en la seccion General del servicio. La ruta es relativa al build path.

Esto es util cuando tienes multiples Dockerfiles para distintos entornos:

mi-proyecto/
  Dockerfile           # Desarrollo
  Dockerfile.prod      # Produccion
  Dockerfile.test      # Testing/CI

Optimizar imagenes para produccion

Usar alpine como base

Alpine Linux produce imagenes significativamente mas pequenas:

# ~900MB
FROM node:22

# ~150MB
FROM node:22-alpine

Si necesitas compilar dependencias nativas en Node.js:

FROM node:22-alpine
RUN apk add --no-cache python3 make g++
COPY package.json package-lock.json ./
RUN npm ci --only=production
RUN apk del python3 make g++

Usar scratch para binarios estaticos

Para Go, Rust u otros lenguajes que producen binarios estaticos:

FROM rust:1.84-alpine AS builder
WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM scratch
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/mi-app /
EXPOSE 8080
ENTRYPOINT ["/mi-app"]

La imagen final pesa solo lo que pesa el binario (tipicamente 5-20MB).

Usar distroless de Google

Un punto medio entre alpine y scratch. Incluye certificados CA y timezone data:

FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /server .

FROM gcr.io/distroless/static-debian12
COPY --from=builder /server /
EXPOSE 8080
ENTRYPOINT ["/server"]

Buenas practicas para imagenes pequeñas

  1. Ordenar las capas: Copiar archivos de dependencias antes que el codigo fuente para aprovechar cache
  2. Combinar RUN: Menos capas = imagen mas pequena
  3. Limpiar cache: Eliminar cache de package managers
  4. Usar .dockerignore: Excluir archivos innecesarios

Ejemplo de .dockerignore:

node_modules
.git
*.md
.env*
dist
coverage
.vscode

Ejemplo: Node.js optimizado para produccion

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force

FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json .

USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

Este patron usa tres stages:

Debugging de builds fallidos

Si el build falla, revisa los logs en Deployments. Problemas comunes:

Archivo no encontrado en COPY:

COPY failed: file not found in build context

Verifica que el build path y dockerfile path esten correctos, y que el archivo no este en .dockerignore.

Dependencia nativa faltante:

node-gyp ERR! build error

Agrega las dependencias de compilacion en el stage de build:

RUN apk add --no-cache python3 make g++

Puerto no expuesto:

Asegurate de que el EXPOSE coincida con el puerto configurado en Dokploy y que tu app escuche en 0.0.0.0, no en 127.0.0.1.


Anterior: Capitulo 6: Nixpacks | Siguiente: Capitulo 8: Docker Compose