En esta guía se muestra el paso a paso sobre cómo instalar y configurar un servidor web en una VPS desde cero. Ahorrandote todos los costes de herramientas de hosting tradicionales y obteniendo un control total sobre tu entorno de despliegue.
Para montar toda la infraestructura necesitaba una base sólida, flexible y, sobre todo, con buena relación calidad‑precio. Después de comparar varias opciones, me decanté por Hetzner, un proveedor que destaca por ofrecer recursos potentes a un coste muy ajustado.
Puede parecer una configuración modesta, pero es más que suficiente para el objetivo que tenía en mente: ejecutar varias aplicaciones web y APIs de forma simultánea utilizando contenedores Docker.
Gracias al uso de Docker y Docker Compose, cada aplicación se ejecuta en su propio contenedor, lo que permite:
Además, con Traefik como reverse proxy, puedo exponer múltiples aplicaciones y APIs bajo diferentes dominios o subdominios sin necesidad de aumentar la complejidad del servidor.
Antes de empezar a desplegar contenedores y servicios, es fundamental preparar correctamente el servidor. Una buena base evita problemas de seguridad, rendimiento y mantenimiento en el futuro.
En mi caso opté por Ubuntu Server LTS, una elección bastante común en servidores por varios motivos:
Nada más crear la VPS, lo primero fue acceder por SSH como usuario root para realizar la configuración inicial.
El primer paso siempre es asegurarse de que el sistema está completamente actualizado:
apt update && apt upgrade -y Con esto nos aseguramos de tener los últimos parches de seguridad y versiones estables de los paquetes instalados.
Por seguridad, es buena práctica no trabajar directamente con el usuario root. Así que creé un usuario nuevo y le asigné permisos de sudo:
adduser deploy Para mejorar la seguridad del servidor, realicé varios ajustes en el acceso SSH:
Después de cualquier cambio, es necesario reiniciar el servicio:
systemctl restart ssh Para limitar el acceso únicamente a los servicios necesarios, configuré el firewall con UFW (Uncomplicated Firewall).
Primero permití los puertos básicos:
ufw allow OpenSSH Y después activé el firewall:
ufw enable Con esto el servidor solo acepta tráfico por SSH y por los puertos HTTP/HTTPS, que más adelante usará Traefik.
Con el servidor ya preparado y asegurado, el siguiente paso es instalar Docker y Docker Compose, que serán la base para ejecutar todas las aplicaciones y servicios de forma aislada y ordenada.
Aunque Ubuntu incluye Docker en sus repositorios, preferí instalarlo desde el repositorio oficial de Docker para asegurarme de tener una versión más actualizada y estable.
1. Instalar paquetes necesarios:apt install -y ca-certificates curl gnupg 2. Añadir la clave GPG oficial de Docker: install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg 3. Configurar el repositorio: echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null 4. Instalar Docker Engine: apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 5. Comprobar la instalación: docker --version
docker compose version Para evitar tener que usar sudo en cada comando de Docker, añadí mi usuario al grupo docker:
usermod -aG docker deploy Tras cerrar sesión y volver a entrar, ya es posible ejecutar Docker directamente con el usuario normal.
En este caso utilicé Docker Compose V2, que viene integrado como plugin oficial de Docker. Esto permite usar el comando:
docker compose sin necesidad de instalar binarios adicionales ni depender de versiones antiguas.
Para validar que todo estaba correctamente instalado, lancé un contenedor de prueba:
docker run hello-world Si todo va bien, Docker descarga la imagen y muestra un mensaje de confirmación.
Con Docker funcionando correctamente, el siguiente paso clave es introducir Traefik como reverse proxy. Traefik se convierte en el punto único de entrada a todas las aplicaciones web y APIs que se ejecutan en el servidor.
Su gran ventaja frente a otras soluciones es que está pensado para entornos con contenedores, detectando automáticamente los servicios a través de Docker.
Elegí Traefik principalmente por:
Antes de levantar Traefik, creé una red Docker externa que será compartida por Traefik y todas las aplicaciones que quiera exponer públicamente:
docker network create traefik Esta red permite que Traefik enrute tráfico hacia contenedores definidos en distintos docker-compose.
Traefik se ejecuta como un contenedor más, normalmente en su propio stack. Un docker-compose.yml simplificado sería:
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--api.dashboard=true"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik
networks:
traefik:
external: true Con esta configuración básica, Traefik ya es capaz de detectar contenedores Docker y enrutar tráfico HTTP y HTTPS.
Traefik utiliza providers para descubrir servicios. En este caso, el provider principal es Docker:
Esto elimina la necesidad de editar archivos de configuración cada vez que se añade una nueva aplicación.
Traefik permite gestionar certificados SSL automáticamente usando Let’s Encrypt. Para ello, solo es necesario:
Esto permite que, al levantar una nueva aplicación con su dominio, Traefik genere y renueve automáticamente los certificados sin intervención manual.
Una vez Traefik está funcionando como reverse proxy, el siguiente paso es ponerlo a trabajar de verdad: exponer una aplicación web real utilizando Docker Compose y las labels de Traefik.
En este punto es donde todo empieza a encajar y se aprecia la potencia de esta arquitectura.
Cada aplicación vive en su propio directorio y tiene su propio docker-compose.yml. Por ejemplo:
Esto permite desplegar, actualizar o eliminar aplicaciones de forma totalmente independiente.
Supongamos una aplicación web sencilla (por ejemplo una API o un frontend). El docker-compose.yml podría ser algo como esto:
services:
app:
image: nginx:alpine
container_name: mi_app_web
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.miapp.rule=Host(`miapp.midominio.com`)"
- "traefik.http.routers.miapp.entrypoints=websecure"
- "traefik.http.routers.miapp.tls.certresolver=letsencrypt"
networks:
- traefik
networks:
traefik:
external: true Con solo estas labels, Traefik:
Las labels son el corazón de la configuración con Traefik. En ellas se define:
Todo esto se hace sin tocar Traefik, simplemente declarando comportamiento desde la propia aplicación.
Levantar la aplicación es tan simple como ejecutar:
docker compose up -d En cuestión de segundos:
Este enfoque permite:
Cada nuevo proyecto sigue exactamente el mismo patrón, lo que simplifica enormemente el mantenimiento a largo plazo.
Después de montar la infraestructura y desplegar varias aplicaciones, hay una serie de buenas prácticas que marcan la diferencia entre un sistema que simplemente funciona y uno que es mantenible, seguro y escalable en el tiempo.
Esto facilita actualizaciones, reinicios y depuración.
Centralizar la exposición pública a través de una red externa (como traefik) permite:
Por defecto, Traefik no expone ningún contenedor (exposedbydefault=false). Solo aquellos servicios que realmente necesitan acceso público deben llevar labels de Traefik.
Guardar los docker-compose.yml en un repositorio (Git) permite:
Evitar perder información al recrear contenedores.
Aunque Docker lo permita, no es buena idea trabajar siempre como root. Usar un usuario dedicado reduce riesgos y errores accidentales.
El dashboard de Traefik es muy útil, pero no debe quedar expuesto públicamente sin autenticación o restricciones de red.
Sin una mínima estrategia de logs:
Al menos conviene revisar periódicamente los logs de Traefik y de las aplicaciones.
Aunque la VPS sea suficiente al inicio, es importante:
Toda esta infraestructura no es solo un experimento o un laboratorio. La propia web que estás leyendo ahora mismo está desplegada exactamente de esta forma:
Cada parte vive en su propio stack, pero todas conviven en la misma VPS de forma ordenada, segura y eficiente.
Después de usar esta arquitectura de forma continuada, las principales ventajas son claras:
Montar un servidor web moderno hoy en día no requiere infraestructuras complejas ni grandes inversiones. Con una VPS bien elegida, Docker, Traefik y una buena organización, es posible construir una plataforma sólida, flexible y preparada para crecer.
Esta arquitectura me permite centrarme en lo importante: desarrollar aplicaciones, sabiendo que la base sobre la que corren es estable, mantenible y fácil de evolucionar.
Si estás pensando en montar tu propio servidor para alojar varias aplicaciones y APIs, este enfoque es una opción más que recomendable.
Si te quedan dudas o quieres compartir tu experiencia, no dudes en dejar un comentario o contactarme directamente. ¡Feliz despliegue!