He traspasado todas mis aplicaciones que tenía en heroku a un vps propio y además he aprovechado para dockerizarlas todas, incluyendo bases de datos y otros servicios web. Concretamente he dockerizado:

Instalación de docker en el VPS

Docker

En primer lugar he tenido que instalar docker y docker compose. Para la instalación de docker en ubuntu 22.04 server :

Actualizamos la lista de paquetes del S.O. y actualizamos el S.O.

apt update && apt upgrade

Añadimos una serie de paquetes de requisitos previos.

apt install apt-transport-https ca-certificates curl software-properties-common

Se agrega la clave GPG para el repositorio oficial de Docker a nuestro S.O.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

Y por último ya podremos añadir el repositorio a nuestro S.O. Podemos comprobar que se ha añadido una línea en /etc/apt/source.list.

add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable"

Actualizamos la base de datos ya que hay una línea nueva (compruebe en el listado que sale) y comprobaremos antes de instalar que vamos a instalar desde el repositorio (y no desde Ubuntu Server).

apt update & apt-cache policy docker-ce

Por último, podremos instalar el paquete (paquete pesado de aproximadamente 400 MBytes). Como no puede ser de otra manera hay que asegurar que Docker engine está arrancado y si reinicia el servicio en el arranque del S.O. anfitrión.

apt-get install docker-ce

Manualillo de systemctl

systemctl disable Docker # deshabilitamos en el arranque el demonio docker 
systemctl list-unit-files | grep docker 
systemctl enable docker 
systemctl list-unit-files | grep docker 
systemctl stop docker && systemctl status Docker 
systemctl start docker && systemctl status Docker 
docker versión

Resolución del problema de permisos de docker para un usuario no root

sudo groupadd docker
sudo usermod -aG docker mi_usuario
newgrp docker
docker ps

Docker-Compose

Actualización de la lista de repositorios

sudo apt update

Instalación de docker-compose

sudo apt install docker-compose

Comprobamos el estado del servidor docker y las versiones respectivamente

systemctl status docker
docker -v
docker-compose -version

Creación de los contenedores y docker-compose

En primer lugar me he creado una carpeta docker en el home, que irá alojando los scripts y ficheros necesarios. Tiene la siguiente estructura:

docker-jerarquia.jpg

Vamos a utilizar para el certificado de autenticación del servidor https Let’s encrypt.

Instalación y generación del certificado Let’s encrypt

Para la instalación de la aplicación certbot de Let’s encrypt para generar los certificados, recomiendo seguir las instrucciones de la siguiente página https://certbot.eff.org/instructions?ws=other&os=ubuntufocal está preseleccionado para Ubuntu server.

La página indicada anteriormente, es la única que funciona actualmente con Ubuntu Server 22.04, el resto de tutoriales que he visitado se encuentran obsoletos al haber cambiado a snap la aplicación de certbot de Let’s encrypt y el repositorio de github ya no funciona tampoco como se esperaba en instrucciones antiguas.

Una vez terminado nos generará la siguiente salida: salida_certificado_letsencrypt

Generamos el certificado que es válido para tomcat .p12.

openssl pkcs12 -export -in /etc/letsencrypt/live/mi_dominio_web/fullchain.pem -inkey /etc/letsencrypt/live/mi_dominio_web/privkey.pem -out springboot_letsencrypt.p12 -name bootalias -CAfile chain.pem -caname root

Recuerda cambiar las rutas por las tuyas

Contenedor para Spring Boot

Copiamos los ficheros necesarios en nuestra carpeta de nuestro nuestra carpeta en el home de /docker/docker-java/: estando en la raíz de esta el fichero .jar y .p12.

Generación de los scripts de docker

En primer lugar, vamos a crear un Dockerfile para que contenga la imagen (para Ubuntu server) y realice la copia de los ficheros necesarios (el .jar y el certificado) hacia nuestro contenedor.

Dockerfile

FROM eclipse-temurin:17-jdk-alpine

ARG JAR_FILE=*.jar

COPY ${JAR_FILE} my_proyect_compiled.jar
COPY my_cert_letsencrypt.p12 .

ENTRYPOINT ["java","-jar","/my_proyect_compiled.jar"]

Tendremos que cambiar el nombre del certificado y del fichero del proyecto compilado .jar

En segundo lugar, vamos a crear el script de docker-compose.yml que creará nuestro servicio web y expondrá el puerto 8443 por defecto.

docker-compose.yml

version: '3.8'
services:
  proyect_name:
    build: .
    container_name: 'proyect_name-spring-app'
    restart: always
    ports:
      - '8080:8080'
      - '8443:8443'

Tendremos que cambiar el nombre del servicio y del contenedor

Proyecto Spring Boot

Vamos a añadir al fichero aplication.properties la configuración para que el servidor tomcat que crea Spring, escuche por el puerto https, que por defecto es el 8443 en lugar del 443. Y el certificado que hemos creado previamente para que nuestro servidor funcione con Let’s encrypt.

aplication.properties

server.port=9443
server.ssl.enabled=true
server.ssl.key-store=my_cert_letsencrypt.p12
server.ssl.key-store-password=your_password
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=your_alias

Tendremos que cambiar: el certificado, la contraseña y el alias con el que creamos anteriormente.

Generamos el fichero .jar mediante el comando de mvn para compilar

mvn package

Para ver la diferencia entre mvn package y mvn install, consulta este enlace

Ahora en la carpeta /target del proyecto tendremos el fichero .jar que pasaremos por ftp a nuestra carpeta en el home de /docker/docker-java/nombre_aplicacion que contendrá a su vez los ficheros de Dockerfile y docker-compose.yml, junto a nuestro certificado .p12. Para que todo se pueda copiar en el contenedor de docker, según las instrucciones de nuesto Dockerfile.

Finalmente, debemos tener la siguiente estructura de ficheros en la carpeta de docker:

/docker-my_app
|-Dockerfile
|-docker-compose.yml
|-my_app.jar
|-my_cert.p12

En la página oficial del Spring Boot hay también un tutorial que indica con mayor detalle lo realizado anteriormente https://spring.io/guides/topicals/spring-boot-docker/ incluyendo aspectos de seguridad como la creación de un usuario diferente a root dentro del contenedor de docker.

Contenedor para Laravel

Para Laravel vamos a utilizar un servidor ngnix con la última versión por seguridad. para ello crearemos varios ficheros que describiré a continuación:

Generación de los scripts de docker

Dockerfile

# imagen de dockerhub que descargara
FROM php:8.2-fpm-alpine

# algunas configuraciones para que funcione el contenedor para mysql
#RUN docker-php-ext-install pdo pdo_mysql

# Install Postgre PDO
RUN set -ex \
  && apk --no-cache add \
    postgresql-dev

RUN docker-php-ext-install pdo pdo_pgsql

# instala composer en el contenedor
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# da permisos para editar los archivos en esta ruta del container
RUN chown -R www-data:www-data /var/www
RUN chmod 755 /var/www

docker-compose.yml

version: "3.3"

# Servidor nginx
services:
  nginx-laravel:
    image: nginx:latest
    restart: always
    ports:
      - "10440:443"
    volumes:
      - ./src:/var/www/html
      - ./src/storage:/var/www/html/storage
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt/live/mi_dominio_web/fullchain.pem:/etc/nginx/ssl/fullchain.pem:ro
      - /etc/letsencrypt/live/mi_dominio_web/privkey.pem:/etc/nginx/ssl/privkey.pem:ro

    links:
      - php-laravel

  # Configuración de php-fpm
  php-laravel:
#    image: php:8-fpm
    build: .
    restart: always
    volumes:
      - ./src:/var/www/html
#    command: sh -c "cd /var/www/html && composer update nesbot/carbon"

nginx.conf

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    server_name mi_ip www.mi_dominio mi_dominio localhost;
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }

    # Log files for Debug
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    # Laravel web root directory
    root /var/www/html/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }

    # Nginx Pass requests to PHP-FPM
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-laravel:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

Sustituye mi dominio mi_dominio por el tuyo

Finalmente, debemos tener la siguiente estructura de ficheros en la carpeta de docker:

/docker-my_app
|-Dockerfile
|-docker-compose.yml
|-default.conf
|-nginx.conf
|-src

Y en la carpeta src vamos a copiar todo el contenido de Laravel.

Levantamos el contenedor de docker como demonio y con la compilación activada

docker-compose up --build -d

Con esto ya tenemos una versión de Laravel Dockerizada y funcionando.

Contenedor para Flask

Tenemos nuestra aplicación python con flask en la carpeta app y los certificados en la carpeta certs y el fichero de requirements al mismo nivel que los de docker.

Generación de los scripts de docker

Dockerfile

version: '3.8'

services:
  flask:
    restart: always
    build: .
    command: python app.py run -h 0.0.0.0
    volumes:
      - ./app:/usr/src/app/
    ports:
      - 10443:5000
    environment:
      - FLASK_APP=app.py
      - FLASK_DEBUG=1

nginx.conf

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    server_name  www.mi_dominio_web mi_dominio_web;
    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }
}

app.py

Sustituye mi dominio mi_dominio_web por el tuyo

Contenedor para Nginx

Copiamos los ficheros necesarios en nuestra carpeta de nuestro nuestra carpeta en el home de /docker/docker-ngnix/: estando en la raíz de esta el fichero docker-compose.yml y nginx.conf, las carpetas: cers, site-content y templates.

Generación de los scripts de docker

docker-compose.yml

version: "3.7"
services:
  web:
    image: nginx
    volumes:
     - ./templates:/etc/nginx/templates
     - ./site-content:/etc/nginx/html
     - ./certs:/etc/nginx/certs:ro
     - ./nginx.conf:/etc/nginx/conf.d/nginx.conf

    ports:
     - "80:80"
     - "443:443"

Generación del script de configuración de ngnix

nginx.conf

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    server_name  www.mi_dominio_web mi_dominio_web;
    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }
}

Sustituye mi dominio mi_dominio_web por el tuyo

Generación del resto de ficheros y carpetas

En la carpeta certs tienes que tener una copia de los ficheros generados por certbot, son los ficheros: fullchain.pem y privkey.pem.

En la carpeta site-content el index.html de tu web con todo su contenido. Este es un ejemplo de redirección de mi página, por si te sirve de ejemplo.

site-content/index.html

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta http-equiv="refresh" content="5;url=https://mi_dominio_web">
</head>

<body>
<h1>Acceso no permitido</h1>
<p>Va a ser redireccionado a la página <a href="https://mi_dominio_web">https://mi_dominio_web</a></p>
</body>
</html>

Contenedor para PostgreSQL

La dockerización de PostgreSQL es una de las más sencillas que hay, solamente tenemos que crear un docker-compose y levantar el contenedor.

Generación del scripts de docker

docker-compose.yml

version: '3.3'
services:
  db:
    image: postgres
    restart: always
    container_name: postgresql
    environment:
      - POSTGRES_USER=mi_usuario
      - POSTGRES_PASSWORD=mi_contraseña
    ports:
      - 'mi_puerto_ext:5432'
    volumes:
      - db:/var/lib/postgresql/data
volumes:
  db:
    driver: local

En la carpeta ./db que se creará automáticamente tendremos todos los ficheros de la base de datos para poder consultar y/o modificar lo que necesitemos desde fuera del contenedor.

Contenedor para mySQL

La dockerización de MySQL es una de las más sencillas que hay, solamente tenemos que crear un docker-compose y levantar el contenedor.

Generación del scripts de docker

docker-compose.yml

version: '2.4'
services:
  mariadb:
    image: mariadb
    container_name: mysql
    restart: always
    volumes:
      - ./db:/var/lib/mysql
    environment:
      TZ: Europe/Madrid
      #MYSQL_RANDOM_ROOT_PASSWORD: "yes"
      MYSQL_ROOT_PASSWORD: mi_contraseña_root
      MYSQL_DATABASE: mi_nombre_bbdd
      MYSQL_USER: mi_usuario
      MYSQL_PASSWORD: mi_contraseña
    ports:
      - 23452:3306
    #healthcheck:
      #test:  mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD || exit 1
      #test:  mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD || exit 1
      #interval: 60s
      #timeout: 5s
      #retries: 5
      #start_period: 30s

En la carpeta ./db que se creará automáticamente tendremos todos los ficheros de la base de datos para poder consultar y/o modificar lo que necesitemos desde fuera del contenedor.

Contenedor para MongoDB

La dockerización de MongoDB es una de las más sencillas que hay, solamente tenemos que crear un docker-compose y levantar el contenedor.

Generación del scripts de docker

docker-compose.yml

version: '3.7'
services:
  mongodb_container:
    image: mongo:latest
    ports:
      - mi_puerto_ext:27017
    volumes:
      - db:/data/db
volumes:
  db:

En la carpeta ./db que se creará automáticamente tendremos todos los ficheros de la base de datos para poder consultar y/o modificar lo que necesitemos desde fuera del contenedor.


Fuentes:

https://certbot.eff.org/instructions

https://wstutorial.com/rest/spring-boot-with-lets-encrypt.html

https://spring.io/guides/topicals/spring-boot-docker/

https://awstip.com/run-nginx-in-a-docker-container-using-pre-generated-ssl-certificates-from-letsencrypt-b005ebce74ca