Con el proyecto dockerizado y en GitHub, toca el servidor. En vez de reutilizar el droplet de la serie manual, este post crea uno nuevo dedicado a correr todo por contenedores — así queda claro qué cambia entre un VPS configurado a mano y uno basado en Docker.
1. Crear el droplet
En DigitalOcean: Create → Droplets, con estas opciones:
- Region: la más cercana a tus usuarios
- Image: Ubuntu 24.04 (LTS)
- Plan: Basic → CPU Option Regular → el de 1 GB RAM / $6 al mes
- Authentication: SSH Key → selecciona la tuya
- Nombre:
servidor-docker - No marques Backups ni ningún extra
Verifica que el Summary diga ~$6.00/month y créalo. Copia la IP que te asignen.
1.2. Entrar y crear tu usuario
ssh root@TU_IP
Escribe yes si pregunta. Ya dentro:
apt update && apt upgrade -y
Si aparece una pantalla morada, presiona Enter.
adduser abel
Pon una contraseña y guárdala. Enter en los datos opcionales y confirma con Y.
usermod -aG sudo abel
rsync --archive --chown=abel:abel ~/.ssh /home/abel
El primero le da permisos de administrador; el segundo le copia tu llave SSH para que puedas entrar con él.
Antes de cerrar nada, abre otra terminal y comprueba:
ssh abel@TU_IP
sudo whoami
Debe responder root tras pedirte la contraseña de abel.
1.3. Desactivar el login de root
Ya como abel:
sudo nano /etc/ssh/sshd_config
Busca la línea PermitRootLogin y déjala así:
PermitRootLogin no
Guarda (Ctrl+O, Enter, Ctrl+X) y aplica:
sudo systemctl restart ssh
Sin cerrar tu sesión, comprueba en otra terminal que ssh root@TU_IP ahora falla y ssh abel@TU_IP sigue funcionando.
1.4. Firewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Escribe y cuando pregunte. El primer comando es crítico: sin él te quedarías fuera del servidor.
Hay una diferencia con el servidor manual: ahí se usaba sudo ufw allow 'Nginx Full', un perfil con nombre amigable que el paquete de Nginx crea al instalarse. En este servidor Nginx vive en un contenedor, así que ese perfil no existe y el comando fallaría. Se abren los puertos directamente, que es exactamente lo mismo.
sudo ufw status
Debe mostrar OpenSSH, 80/tcp y 443/tcp permitidos.
1.5. Actualizaciones de seguridad automáticas
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Yes en la pantalla azul.
2. Apuntar el dominio a la IP nueva
En DigitalOcean: Networking → Domains → tudominio.com.
Verás los dos registros A de un servidor anterior, apuntando a la IP vieja. En el menú de cada uno (los tres puntos), elige editar y pon la IP nueva. Son dos: el de hostname @ y el de www.
No toques nada en el registrador del dominio; los nameservers siguen igual.
Fíjate en lo que no se instaló: ni Node, ni Postgres, ni Nginx, ni PM2, ni Certbot. Todo eso ahora viaja dentro de los contenedores.
3. Instalar Docker en el servidor
Ubuntu trae una versión de Docker en sus repositorios, pero está desactualizada y no incluye Compose. Se instala desde el repositorio oficial de Docker, que es la forma recomendada en producción.
Como abel, ejecuta esto en bloque:
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
Eso descarga la llave GPG de Docker. Sirve para que el sistema verifique que los paquetes vienen realmente de Docker y no de alguien que los suplantó por el camino.
Ahora agrega el repositorio:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
E instala:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Comprueba que funciona:
sudo docker run hello-world
Debe imprimir un mensaje de bienvenida.
Para que no haga falta sudo en cada comando:
sudo usermod -aG docker abel
Eso agrega a abel al grupo docker. Para que surta efecto, sal y vuelve a entrar:
exit
Conéctate de nuevo y verifica:
docker run hello-world
docker compose version
Algo que debes saber: pertenecer al grupo docker equivale prácticamente a ser root, porque desde un contenedor se puede montar cualquier carpeta del sistema. Es aceptable aquí porque abel ya tiene sudo, pero ten esto presente si algún día le das acceso a alguien más: agregarlo al grupo docker es darle control del servidor.
4. Bajar el código al servidor
El servidor necesita una identidad para hablar con GitHub, igual que en un despliegue manual. Como abel:
ssh-keygen -t ed25519 -C "servidor-docker"
Enter en todas las preguntas.
cat ~/.ssh/id_ed25519.pub
Copia esa línea completa.
En GitHub, entra a tu repositorio → Settings → Deploy keys → Add deploy key. Pega la llave, ponle el título servidor-docker y deja desmarcado “Allow write access”, porque el servidor solo necesita leer.
Si todavía existe la deploy key de un servidor anterior, bórrala: ya no existe esa máquina.
Comprueba la conexión:
ssh -T git@github.com
Escribe yes si pregunta.
Ahora clona el proyecto:
cd ~
git clone git@github.com:TU-USUARIO/todo-produccion.git app
cd app
ls -la
Deberías ver el Dockerfile, docker-compose.yml, las carpetas nginx y db, y el .env.example.
Fíjate en que no está el .env, y eso es correcto: nunca subió a GitHub. El siguiente paso es crearlo aquí, con credenciales distintas a las de tu máquina local, porque esto es producción.
nano .env
Pega esto y pon una contraseña larga y nueva:
POSTGRES_DB=todoapp
POSTGRES_USER=todouser
POSTGRES_PASSWORD=una_clave_larga_y_distinta_de_la_local
Guarda y protégelo:
chmod 600 .env
Ese archivo es el único secreto del servidor, y con esos permisos solo abel puede leerlo.
4.1. Levantar el sistema
cd ~/app
docker compose up -d --build
La primera vez tardará unos minutos: descarga las imágenes base y construye la propia. El -d lo deja en segundo plano.
Comprueba el estado:
docker compose ps
Los tres servicios deben aparecer como “running”, y db además como “healthy”.
Ahora la prueba real. En el navegador entra a:
http://tudominio.com
Debería aparecer la app, funcionando desde contenedores (todavía sin HTTPS — eso es el siguiente post).
Si algo falla, el comando que dice qué pasó es:
docker compose logs --tail=50
O para un servicio concreto:
docker compose logs app --tail=50