Continuamos con el VPS! Hoy instalaremos un servidor web con Nginx.
¿Qué es un servidor web realmente? Es un proceso que se ejecuta y expone (normalmente) el puerto 80 y el 443 que son los más usados para páginas web, y es configurado para que al visitar el servidor con un navegador web, nos muestre el código alojado en un directorio concreto.
Algo que no sabía, o mejor dicho no recordaba o no había caído en la cuenta porque al final esto lo hacemos siempre con herramientas ya preparadas, es que los servidores web vienen por defecto únicamente preparados para trabajar con HTML. Para otros lenguajes, hará falta un plugin o alguna configuración adicional.
Instalar Nginx
Como hemos comentado, lo haremos todo con Docker. La idea es más adelante contratar un dominio baratito y configurar el ssl, su servidor de correo y demás. Así que como vamos a necesitar varias imágenes, tiro con Docker Compose directamente.
NOTA: en la parte 1, había dicho que quería hacerlo muy gradual, empezando por comandos de Docker (docker run -it…..) pero la realidad es que al final pocas veces necesitaré hacer eso, y esto es un juego para aprender así que lo haré al revés. Primero con Docker Compose, y desde ahí saco los comandos de Docker a secas.
Así me creo un docker-compose.yaml en ~/webserver:
version: '3'
services:
www:
image: nginx
restart: always
ports:
- "80:80"
volumes:
- ./www/public:/usr/share/nginx/html:ro
A tener en cuenta:
- El servicio se llamará www
- La imagen usada es nginx
- Expone los puertos 80 del contenedor al 80 del host (del servidor)
- El volumen de persistencia será el mismo directorio donde está el docker-compose.yaml y dentro www/public del host, del servidor (será document root del servidor) mapeado al directorio /usr/share/nginx/html del contenedor (document root por defecto de cualquier servidor Nginx).
- ro para que sea de solo lectura
Ahora «instalamos» Nginx, que realmente es ejecutar una imagen Docker. Ejecutamos en el dir donde está el docker-compose.yaml:
docker compose up -d
Y con esto tenemos Nginx funcionando. Lo comprobamos?
- desde el servidor ejecutamos
- curl 127.0.0.1
- nos devuelve un error 403 de nginx porque no hemos colocado ningun fichero para mostrar, está ok
- desde nuestra máquina ejecutamos
- curl remote_host
- siendo remote_host o bien el dominio que apunte a la IP del servidor o la propia IP del servidor
- devuelve lo mismo
- curl remote_host
Cositas básicas de Nginx
Aquí realmente no me haría falta configurar nada más, ya que la idea justamente de un contenedor de Docker de este tipo es poder ser portable… me llevo el directorio con el Dockerfile a cualquier sitio y el servidor se monta con un comando.
Pero como la idea es aprender lo básico al menos, quiero indagar un poco sobre 2 o 3 cosas:
- ¿Cómo configuro un proxy? Tengo entendido (con mi conocimiento actual vamos…) que es algo muy utilizado y útil, y que nos permitiría gestionar el tráfico recibido y enviarlo a distintos virtualhosts según x o y condiciones (un proxy básicamente…)
- ¿Cómo se configuran los virtual hosts en Nginx? Para básicamente tener más de 1 dominio que pueda acceder a la misma IP de este servidor
- ¿Cómo configuro un certificado SSL? Para esto tendré que sí o sí comprarme un dominio de lo más baratito que encuentre. Porque los virtualhosts y el proxy añadiendo la IP del servidor a mi hosts me apaño, pero el SSL necesita un dominio real…
Por supuesto quiero saber toquetear el nginx.conf y demás… Así que, vamos a empezar por hacer esto con Docker y después sí que sí a mano.
Nginx proxy y Virtual hosts, con otra imagen Docker
Tenemos una imagen de Docker, jwilder/nginx-proxy que es muy sencilla de configurar y usar.
La idea es que levanta un contendor con Nginx adicional, que será el que reciba el tráfico (el que tiene expuesto el puerto 80 al exterior) y envía el tráfico que pasa por él al otro contenedor con Nginx (que no expone el puerto 80 al exterior solo a la red interna de Docker para que sean alcanzables entre ellos mismos los contenedores).
Su docker-compose.yaml sería asi:
version: '3'
services:
nginx-proxy:
image: jwilder/nginx-proxy
restart: always
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
www:
image: nginx
restart: always
expose:
- "80"
volumes:
- ./www/public:/usr/share/nginx/html:ro
environment:
- VIRTUAL_HOST=nombredominio.com,www.nombredominio.com
El propio proxy es el servicio nginx-proxy:
- exponemos puertos 80
- montamos como volúmen, el propio socket de Docker (porque en su montaje y puesta en marcha, este contenedor necesita preguntarle a Docker qué contenedor tiene una variable de entorno VIRTUAL_HOST para poder montar el virtual host correctamente, y redireccionar tráfico a ese contenedor en concreto)
El servicio de nginx modificamos lo siquiente:
- en lugar de ports: «80:80», usamos expose: «80» que solo expone el puerto para la red de Docker
- agregamos environment para crear una variable de entorno (será leída por el contenedor de nginx-proxy para redireccionar el tráfico correctamente)
De esta sencilla forma, el tráfico que venga desde nombredominio.com, será redirigido al contenedor www que tiene la variable de entorno VIRTUAL_HOST que conicide con ese dominio. Podríamos crear otro contenedor (www2) con otra variable de entorno para otro dominio y redirigiría el tráfico de ambos dominios correctamente.
Para probarlo simplemente añado una línea en mi hosts para la IP de mi servidor y el dominio
Certificado SSL
Es mucho más sencillo de lo que pensaba… Docker trae mucha magia cuando usas una imagen concreta.
Podemos crear el certificado SSL automáticamente con la imagen jrcs/letsencrypt-nginx-proxy-companion que necesita un proxy para funcionar correctamente (cosa que ya tenemos hecha).
¿Qué necesita esta imagen para funcionar?
Un contenedor con nginx proxy (porque es un «compañero» para el nginx proxy) y algunos detalles del mismo:
- añadir un label concreto a este contenedor
- volumen de los virtual hosts del nginx en este contenedor (para que el letsencrypt pueda escribir en ellos)
- volumen del directorio nginx/html (para que letsencrypt pueda hacer la validación mediante subida de archivos al document root)
En el propio contenedor de letsencrypt-companion nos pide:
- volumen para los certificados (mapeado a /etc/nginx/certs por defecto)
- volumen del socket de Docker para que pueda leer todo esto de los otros contenedores (igual que el contendor del proxy)
- importar todos los volumenes del contenedor nginx-proxy
En el contenedor del servidor nginx:
- variable de entorno LETSENCRYPT_HOST (dominio para el certificado) y LETSENCRYPT_MAIL (allí donde recibiremos el aviso de que hay que renovar el certificado)
El docker-compose.yaml sería así:
version: '3'
services:
nginx-proxy:
image: jwilder/nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- certs:/etc/nginx/certs:ro
- vhostd:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
labels:
- com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
restart: always
environment:
- NGINX_PROXY_CONTAINER=nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- vhostd:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- /var/run/docker.sock:/var/run/docker.sock:ro
www:
image: nginx
restart: always
expose:
- "80"
volumes:
- ./www/public:/usr/share/nginx/html:ro
environment:
- VIRTUAL_HOST=nombredominio.com,www.nombredominio.com
- LETSENCRYPT_HOST=nombredominio.com,www.nombredominio.com
- LETSENCRYPT_EMAIL=nombre@dominio.com
depends_on:
- nginx-proxy
- letsencrypt
volumes:
certs:
html:
vhostd:
Por supuesto hemos añadido el puerto 443 en el nginx-proxy también.
Levantamos contenedores con docker compose up -d y ocurrirá esto:
- el contenedor de letsencrypt va a detectar el contenedor que tiene las variables de entorno LETSENCRYPT_HOST y LETSENCRYPT_EMAIL
- se fijará en el directorio certs y como no encontrará nada, generará los certificados conectándose a Letsencrypt y colocará dichos certificados en directorio certs
- reiniciará el contenedor de nginx
- modificará el virtualhost correspondiente para que al visitar el sitio en cuestión sin https, fuerce la redirección a la versión https
- cada hora intentará renovar el certificado, por lo que nunca caducará
Podemos comprobar que todo ha ido bien revisando los logs del contenedor de letsencrypt con
docker compose logs letsencrypt
¿Y ahora qué?
Bueno, tengo que dar mérito al canal de youtube de Pelado Nerd muy muy muy recomendado, de donde he sacado toda esta información. Ha sido super entretenido y he aprendido bastante tanto de Docker como de Nginx y certificados, pero… quiero hacerlo manualmente que es cuando lo acabaré de entender, sin docker.
Quizás lo deje instalado con Docker y modifique algunos archivos aunque al destruir contenedores se pierda el cambio… al final lo que quiero es saber donde tocar cada cosa, o lo monte bien en volúmenes de forma que persista el cambio. Veré cuando me ponga con esta parte. Será replicar esto mismo pero sin Docker.