Acceso Seguro a MySQL en Minutos con Docker y VPS

Introducción: El Pedido Urgente de un Cliente
Imagina este escenario: estás gestionando un proyecto en un VPS de Digital Ocean, con una aplicación corriendo suavemente en Docker y una base de datos MySQL alojada en su servicio gestionado. Todo va sobre ruedas hasta que, de pronto, llega un mensaje del cliente: "¡Necesito acceso directo a la base de datos para análisis operativos, y lo necesito ya!". Suena simple, pero hay obstáculos. La base de datos de Digital Ocean no viene con una interfaz web tipo DBMS para explorarla. Está protegida por IP, pero el proveedor de internet del cliente rota las direcciones constantemente. Además, Digital Ocean no permite crear usuarios con permisos específicos ni limitar permisos como "solo lectura" desde su interfaz, y el cliente está presionando por una solución rápida. ¿Qué haces? Te cuento cómo lo resolví en menos de 25 minutos, dejando al cliente feliz y con una solución sólida bajo el brazo.
¿Por Qué No Dar Acceso Directo? La Seguridad Primero
Antes de lanzarme a teclear comandos, me detuve a pensar: ¿por qué no simplemente darle las credenciales del administrador de la base de datos y listo? La respuesta es clara: seguridad. Dar acceso directo sería como dejar la puerta de tu casa abierta con un cartel que dice "entren cuando quieran". Digital Ocean, al ser un servicio gestionado, no te permite crear usuarios con permisos específicos ni contraseñas personalizadas desde su interfaz. Proteger el acceso por IP no era viable, porque las IPs del cliente cambiaban todo el tiempo. Si le daba acceso completo, ¿qué pasaría si, en un descuido, ejecuta un DROP TABLE
o un DELETE FROM
y borra datos críticos? Podría ser un desastre para su operación y un dolor de cabeza para mí. Así que decidí construir una solución controlada y segura, aprovechando herramientas que ya tenía a mano.
El Procedimiento Paso a Paso: Navegando con Docker, nginx-proxy y Let’s Encrypt
Paso 0: La Infraestructura Original en docker-compose.yml
Antes de añadir phpMyAdmin, mi archivo docker-compose.yml
ya incluía servicios clave como nginx-proxy
y letsencrypt
, que gestionaban el enrutamiento y la seguridad de mis aplicaciones web. Aquí está el estado original del archivo:
services:
nginx-proxy:
image: jwilder/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /certs:/etc/nginx/certs:ro
- /etc/nginx/vhost.d:/etc/nginx/vhost.d:rw
- /usr/share/nginx/html:/usr/share/nginx/html:rw
- /var/run/docker.sock:/tmp/docker.sock:ro
labels:
- com.github.nginx-proxy.nginx=true
networks:
- proxy-network
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: nginx-proxy-letsencrypt
volumes:
- /certs:/etc/nginx/certs:rw
- /etc/nginx/vhost.d:/etc/nginx/vhost.d:rw
- /etc/acme.sh:/etc/acme.sh:rw
- /usr/share/nginx/html:/usr/share/nginx/html:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- nginx-proxy
networks:
- proxy-network
customer-app:
image: josefo727/customer-app
container_name: customer-app-container
environment:
- VIRTUAL_HOST=customer-app.jose-gutierrez.com
- LETSENCRYPT_HOST=customer-app.jose-gutierrez.com
- LETSENCRYPT_EMAIL=josefo727@gmail.com
volumes:
- ./../customer-app:/app
- ./../customer-app/docker/supervisord.conf:/opt/docker/etc/supervisor.d/system.conf
depends_on:
- nginx-proxy
- redis
networks:
- proxy-network
networks:
proxy-network:
driver: bridge
En este punto, nginx-proxy
ya estaba configurado como un guardia de tráfico, redirigiendo las solicitudes a los contenedores correctos según el dominio especificado en VIRTUAL_HOST
. Por su parte, letsencrypt
se encargaba de generar certificados SSL automáticamente para cada dominio definido en LETSENCRYPT_HOST
. Esta base me permitió añadir nuevos servicios web de forma rápida y segura.
Paso 1: Conectar y Crear un Usuario Seguro
Primero, me conecté a la base de datos MySQL desde la consola usando las credenciales de administrador que Digital Ocean proporciona. El comando fue algo así:
mysql -h db-mysql-customer-do-user-12345678-0.c.db.ondigitalocean.com -P 25060 -u superadmin -p
Una vez dentro, creé un usuario específico para el cliente, restringiendo su acceso a la IP de mi VPS y limitando sus permisos a solo lectura:
CREATE USER 'customer_user'@'vps-ip' IDENTIFIED BY 'secure-password';
GRANT SELECT ON customer_db.* TO 'customer_user'@'vps-ip';
¿Por qué solo SELECT
? Para evitar accidentes. Si el cliente, en un momento de prisa, ejecutara un comando destructivo como DELETE FROM
, los datos críticos estarían en riesgo. Con permisos de solo lectura, le doy una herramienta para analizar, no para destruir.
Para asegurarme de que todo estuviera listo, también visité la interfaz de DigitalOcean (DO). Seleccioné el motor SQL correspondiente, busqué la lista de usuarios, y reseteé la contraseña del usuario customer_user
. Esto garantiza que las credenciales sean válidas y seguras, especialmente porque DigitalOcean genera contraseñas robustas. Este paso rápido en la web de DO me dio tranquilidad antes de continuar.
Paso 2: Configurar phpMyAdmin con Docker
El cliente necesitaba una interfaz web, así que elegí phpMyAdmin. Como ya tenía un entorno Docker, integrarlo fue sencillo. Añadí este bloque a mi docker-compose.yml
:
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: customer-db-container
environment:
- PMA_HOST=db-mysql-customer-do-user-12345678-0.c.db.ondigitalocean.com
- PMA_ARBITRARY=0
- PMA_PORT=25060
- PMASSL=1
- VIRTUAL_HOST=customer-db.jose-gutierrez.com
- LETSENCRYPT_HOST=customer-db.jose-gutierrez.com
- LETSENCRYPT_EMAIL=josefo727@gmail.com
restart: always
depends_on:
- nginx-proxy
networks:
- proxy-network
No incluí usuario ni contraseña en el archivo, dejando que phpMyAdmin solicite las credenciales al iniciar sesión. Esto me permitió usar el usuario customer_user
que creé, manteniendo la seguridad y el control.
Paso 3: La Magia de nginx-proxy y Let’s Encrypt
Aquí entra en juego la potencia de mi infraestructura existente. Al añadir el bloque de phpMyAdmin con VIRTUAL_HOST=customer-db.jose-gutierrez.com
, nginx-proxy
detectó automáticamente el nuevo servicio y comenzó a redirigir el tráfico desde ese subdominio al contenedor de phpMyAdmin. Al mismo tiempo, letsencrypt
vio la variable LETSENCRYPT_HOST=customer-db.jose-gutierrez.com
y generó un certificado SSL para el dominio, asegurando una conexión cifrada. Este proceso fue automático gracias a la configuración previa en el docker-compose.yml
original, sin necesidad de ajustar configuraciones manuales de Nginx o certificados.
Paso 4: El Archivo docker-compose.yml
Actualizado
Tras añadir el bloque de phpMyAdmin, mi archivo docker-compose.yml
quedó así:
services:
nginx-proxy:
image: jwilder/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /certs:/etc/nginx/certs:ro
- /etc/nginx/vhost.d:/etc/nginx/vhost.d:rw
- /usr/share/nginx/html:/usr/share/nginx/html:rw
- /var/run/docker.sock:/tmp/docker.sock:ro
labels:
- com.github.nginx-proxy.nginx=true
networks:
- proxy-network
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: nginx-proxy-letsencrypt
volumes:
- /certs:/etc/nginx/certs:rw
- /etc/nginx/vhost.d:/etc/nginx/vhost.d:rw
- /etc/acme.sh:/etc/acme.sh:rw
- /usr/share/nginx/html:/usr/share/nginx/html:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- nginx-proxy
networks:
- proxy-network
customer-app:
image: josefo727/customer-app
container_name: customer-app-container
environment:
- VIRTUAL_HOST=customer-app.jose-gutierrez.com
- LETSENCRYPT_HOST=customer-app.jose-gutierrez.com
- LETSENCRYPT_EMAIL=josefo727@gmail.com
volumes:
- ./../customer-app:/app
- ./../customer-app/docker/supervisord.conf:/opt/docker/etc/supervisor.d/system.conf
depends_on:
- nginx-proxy
- redis
networks:
- proxy-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: customer-db-container
environment:
- PMA_HOST=db-mysql-customer-do-user-12345678-0.c.db.ondigitalocean.com
- PMA_ARBITRARY=0
- PMA_PORT=25060
- PMASSL=1
- VIRTUAL_HOST=customer-db.jose-gutierrez.com
- LETSENCRYPT_HOST=customer-db.jose-gutierrez.com
- LETSENCRYPT_EMAIL=josefo727@gmail.com
restart: always
depends_on:
- nginx-proxy
networks:
- proxy-network
networks:
proxy-network:
driver: bridge
El nuevo servicio de phpMyAdmin se integró perfectamente con nginx-proxy
y letsencrypt
, aprovechando sus capacidades para enrutamiento y seguridad sin requerir ajustes adicionales.
Paso 5: Ejecutar y Probar
Con el archivo actualizado, ejecuté:
docker-compose up -d --remove-orphans --build
En menos de un minuto, el contenedor de phpMyAdmin estaba funcionando. Fui a customer-db.jose-gutierrez.com
en mi navegador, inicié sesión con las credenciales de customer_user
, y todo estaba listo. Le envié las credenciales al cliente, quien pudo acceder y explorar sus datos de inmediato.
Conclusión: Ganar-Ganar con Docker y docker-compose
Resolver este desafío en menos de 25 minutos fue una victoria doble: el cliente quedó satisfecho con una solución rápida y segura, y yo reafirmé el poder de Docker y docker-compose
. Mostrar el docker-compose.yml
antes y después deja claro cómo nginx-proxy
y letsencrypt
simplifican la adición de servicios como phpMyAdmin. Estas herramientas son como un barco veloz que te lleva a puerto sin tormentas: con contenedores, puedo escalar servicios en minutos, mantener todo organizado en un solo archivo, y asegurar conexiones con SSL sin esfuerzo. Si estás empezando, te invito a probar estas tecnologías: son una puerta a la innovación con tiempo de sobra para disfrutar el viaje. ¡Cliente feliz, yo con una solución eficiente, y tú, quizás, con ganas de sumergirte en el mundo de los contenedores!