todos los sistemas operativostodo operativo

apenaloza.cl · bitácora

SCL (UTC−3) · v2026.10

~/blog/de-localhost-a-produccion-vps-dns-docker.md

De localhost a producción sin llorar: VPS, DNS, hosting y Docker para desarrolladores

Guía práctica para sacar un proyecto a producción: cómo asegurar un VPS desde cero, configurar DNS y Cloudflare, elegir entre hosting compartido, VPS o cloud, y levantar tu base de datos local con Docker Compose en segundos.

autor
Alejandro Peñaloza
Publicado
Lectura
6 min
localhost-produccion
En este artículo
  1. Configurar un VPS desde cero
  2. Dominios y DNS explicados para devs
  3. Hosting compartido vs. VPS vs. cloud
  4. Docker para el día a día: tu base de datos local en 30 segundos
  5. Conclusión

Programar la aplicación es la mitad del trabajo. La otra mitad empieza cuando hay que sacarla de localhost y dejarla corriendo en internet, con dominio, HTTPS y sin que el primer bot que pase por ahí entre a tu servidor.

En este post reúno lo que yo mismo hago cada vez que monto un proyecto: cómo dejar un VPS seguro desde cero, cómo entender el DNS de una vez, cuándo conviene cada tipo de hosting y cómo uso Docker para no ensuciar mi máquina con bases de datos.

Configurar un VPS desde cero

Los ejemplos son para Ubuntu Server 24.04, pero aplican casi igual a Debian.

1. Primer acceso y usuario sin privilegios

Lo primero es dejar de usar root para el día a día.

# Conectado como root
apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy

2. Llaves SSH en lugar de contraseñas

Desde tu máquina local:

ssh-keygen -t ed25519 -C "tu-correo@ejemplo.com"
ssh-copy-id deploy@IP_DEL_SERVIDOR

Prueba que entras con la llave antes de tocar la configuración de SSH.

3. Endurecer SSH

Edita /etc/ssh/sshd_config (o crea un archivo en /etc/ssh/sshd_config.d/):

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Importante: antes de reiniciar el servicio, deja otra sesión SSH abierta y verifica que tienes acceso alternativo (por ejemplo, la consola web de tu proveedor). Si algo queda mal configurado, esa sesión abierta es la que te salva.

sudo sshd -t            # valida la sintaxis
sudo systemctl restart ssh

Abre una nueva terminal y confirma que entras. Recién ahí cierra la sesión antigua.

4. Firewall con UFW

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Ojo: si usas Docker, los puertos publicados con -p pueden saltarse UFW porque Docker escribe sus propias reglas de iptables. La solución más simple es publicar los contenedores solo en 127.0.0.1 y dejar que Nginx sea la única puerta de entrada:

ports:
  - "127.0.0.1:3000:3000"

5. Fail2ban y actualizaciones automáticas

sudo apt install fail2ban unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

Fail2ban bloquea IPs que fallan el login repetidamente; unattended-upgrades aplica los parches de seguridad solo.

6. Nginx como proxy inverso

sudo apt install nginx -y

Archivo /etc/nginx/sites-available/miapp:

server {
    listen 80;
    server_name miapp.cl www.miapp.cl;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
sudo ln -s /etc/nginx/sites-available/miapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

7. HTTPS gratis con Let's Encrypt

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d miapp.cl -d www.miapp.cl
sudo certbot renew --dry-run

Certbot modifica el bloque de Nginx, agrega la redirección de HTTP a HTTPS y deja un temporizador que renueva el certificado antes de que venza.

Checklist rápido

  • Usuario sin privilegios con sudo
  • Login solo con llave SSH, root deshabilitado
  • UFW activo con 22, 80 y 443
  • Contenedores expuestos solo en 127.0.0.1
  • Fail2ban y actualizaciones automáticas
  • Nginx + certificado SSL con renovación probada

Dominios y DNS explicados para devs

Comprar el dominio es fácil. Lo que confunde es qué registro poner y dónde.

Los registros que realmente vas a usar

Registro Para qué sirve Ejemplo
A Apunta un nombre a una IPv4 miapp.cl → 203.0.113.10
AAAA Lo mismo, para IPv6 miapp.cl → 2001:db8::10
CNAME Un nombre es alias de otro nombre www → miapp.cl
MX Servidores que reciben el correo miapp.cl → mx1.proveedor.com
TXT Texto libre: verificación, SPF, DKIM, DMARC v=spf1 include:... ~all
CAA Qué autoridades pueden emitir certificados 0 issue "letsencrypt.org"

Dos reglas que ahorran horas:

  1. Un CNAME no puede convivir con otros registros del mismo nombre. Por eso el dominio raíz (miapp.cl) normalmente usa un registro A, no un CNAME. Cloudflare resuelve esto con CNAME flattening.
  2. El TTL manda. Si vas a migrar de servidor, baja el TTL a 300 segundos un día antes; así el cambio se propaga en minutos y no en horas.

Dominios .cl y delegación a Cloudflare

En NIC Chile compras el dominio y, en la sección de servidores DNS, reemplazas los que vienen por defecto por los dos nameservers que te asigna Cloudflare al agregar el sitio. Desde ese momento todos los registros se administran en Cloudflare.

Para revisar que todo propagó:

dig miapp.cl NS +short
dig miapp.cl A +short
dig www.miapp.cl CNAME +short

Sacarle el jugo a Cloudflare

  • Proxy (nube naranja): oculta la IP real del servidor y activa caché y protección. Para registros que no son web (correo, SSH) déjalo en gris.
  • Modo SSL "Full (strict)": cifra el tramo navegador–Cloudflare y también Cloudflare–servidor, validando tu certificado de Let's Encrypt. Evita el modo "Flexible", que deja el tramo al servidor sin cifrar y suele provocar bucles de redirección.
  • Reglas de caché: cachea de forma agresiva /assets/*, /_next/static/* o /build/*, y salta la caché para /api/* y /admin/*.
  • Protección básica: el plan gratuito ya incluye mitigación DDoS. Suma reglas de WAF para limitar intentos en /login y un desafío para países o ASN de donde nunca recibes tráfico legítimo.
  • Cloudflare Tunnel: permite publicar un servicio que corre en tu casa o en una red sin IP pública, sin abrir puertos en el router.
cloudflared tunnel login
cloudflared tunnel create homelab
cloudflared tunnel route dns homelab app.miapp.cl
cloudflared tunnel run homelab

Hosting compartido vs. VPS vs. cloud

No existe "el mejor hosting"; existe el adecuado para el presupuesto, el tráfico y el tiempo que tienes para administrarlo.

Hosting compartido VPS Cloud administrado (PaaS / serverless)
Costo típico Muy bajo y fijo Bajo a medio, fijo Desde gratis, crece con el uso
Control Mínimo (cPanel, PHP) Total (root) Medio: decides código, no servidor
Mantenimiento Lo hace el proveedor Lo haces tú Casi nulo
Escalado Limitado Manual (subes de plan) Automático
Ideal para WordPress, sitios simples APIs, varios proyectos, Docker Frontends, picos de tráfico, equipos sin DevOps

Mi forma de decidir:

  • Sitio informativo o WordPress de un cliente pequeño: hosting compartido. Pagar un VPS para eso es pagar por administrar algo que no necesitas.
  • Varios proyectos propios, APIs, bases de datos y workers: un VPS con Docker. Por un costo mensual fijo corres muchos servicios y el gasto es predecible.
  • Frontend en Next.js o un producto con tráfico muy variable: una plataforma administrada. Pagas más por unidad, pero no pierdes tiempo en servidores.
  • Tráfico serio y requisitos de disponibilidad: cloud (AWS, GCP, Azure, HuaweiCloud) con balanceadores, réplicas y almacenamiento de objetos.

Un detalle que casi nadie compara: el costo de salida de datos (egress). En un VPS suele venir una cuota mensual generosa incluida; en la nube tradicional se cobra por GB y puede ser la línea más cara de la factura. Revisa siempre los precios vigentes del proveedor antes de decidir, porque cambian a menudo.

Docker para el día a día: tu base de datos local en 30 segundos

Instalar PostgreSQL, MySQL y Redis directamente en el sistema funciona hasta que necesitas dos versiones distintas para dos proyectos. Con Docker Compose cada proyecto trae su propio entorno.

docker-compose.yml:

services:
  postgres:
    image: postgres:16-alpine
    container_name: dev-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: app
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./docker/init:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev -d app"]
      interval: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    ports:
      - "127.0.0.1:6379:6379"

  adminer:
    image: adminer
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  pgdata:

Comandos que uso todos los días:

docker compose up -d              # levantar todo
docker compose ps                 # ver estado
docker compose logs -f postgres   # seguir logs
docker compose exec postgres psql -U dev -d app
docker compose down               # apagar (los datos quedan en el volumen)
docker compose down -v            # apagar y borrar datos: empezar de cero

Buenas prácticas:

  • Pon cualquier .sql en ./docker/init/ y se ejecutará solo la primera vez que se cree el volumen. Ideal para esquemas y datos de prueba.
  • Expón los puertos en 127.0.0.1 para que la base de datos no quede accesible desde tu red.
  • Mueve credenciales a un archivo .env y agrégalo a .gitignore.
  • Fija versiones (postgres:16-alpine, no latest) para que todo el equipo tenga exactamente el mismo motor.

Conclusión

Sacar un proyecto a producción no tiene que ser una tarde de sufrimiento. Con un checklist de seguridad para el VPS, un entendimiento claro de los registros DNS, el tipo de hosting correcto y Docker para el entorno local, pasas de localhost a un dominio con HTTPS en una mañana, y con la tranquilidad de saber qué hiciste y por qué.

compartir:linkedinx

Comentarios

Aún no hay comentarios. ¿Quieres dejar el primero?

Deja un comentario

Los comentarios se revisan antes de publicarse. No pido correo ni guardo tu IP.

$ ls ~/blog | grep relacionados

Sigue leyendo