Como realizei o deploy do meu portfólio utilizando uma VPS
December 22, 2024

Como realizei o deploy do meu portfólio utilizando uma VPS

I chose to use a VPS mainly because of the cost benefits it offers. With this option, I had the opportunity to learn and test in a controlled environment and avoid financial surprises at the end of the month. The flexibility to set up and test different scenarios allows me to replicate real-life situations, promoting learning and skill development in a practical and effective way, all within a predictable budget. This option is perfect for those who want to explore new opportunities without spending too much money.


1. Rent a VPS service to obtain dedicated resources.

There are several options, but in my case, I went with the Contabo service, which for just $5 per month, offers 4 CPU cores, 6 GB RAM, 400 GB SSD storage, 200 Mbit/s network speed, and 32 TB per Monthly traffic (unlimited access)

Using the IP of the contracted machine, use the following command on the terminal to access the server for the first time through the SSH protocol:

ssh root@
Enter full screen mode

Exit full screen mode

Use the command to find your machine’s IP to set DNS

ip addr
Enter full screen mode

Exit full screen mode

Set up DNS on the service where you purchased the domain. For detailed guidance on how to perform this configuration, see the provider’s official documentation.


2.Update package

sudo apt update && sudo apt upgrade -y
Enter full screen mode

Exit full screen mode


3. Create new user

Working directly with the root user may expose your server to unnecessary risks, best practice is to create a new user with limited permissions.

adduser newuser
Enter full screen mode

Exit full screen mode


4. Grant sudo permissions

After creating the user, we have to add it to the sudo group so that it can execute commands that require advanced privileges without using the root user directly

usermod -aG sudo newuser
Enter full screen mode

Exit full screen mode

Now we can switch to the new user to start setting up the system

su - newuser
Enter full screen mode

Exit full screen mode


5. Remove SSH password authentication

To improve the security of SSH connections, it is recommended to disable password authentication, which eliminates the risk of password-based brute force attacks.

Before disabling password authentication for SSH, we must create an SSH key pair to ensure that we can still access the server

ssh-keygen -t ed25519 -C "useremail@email.com"
## -t ed25519: Especifica o tipo da chave a ser gerada. 
## -C "seuemail@exemplo.com: Adiciona um comentário à chave Durante a execução do comando, você deverá escolher o local onde deseja salvar a chave ou optar pelo local padrão: `~/.ssh/chave`
Enter full screen mode

Exit full screen mode

After creating the key, we must add the SSH key to the server

ssh-copy-id -i ~/.ssh/sshkey.pub newuser@serverip
Enter full screen mode

Exit full screen mode

  • Indicates the path where your key is located locally -i ~/.ssh/sshkey.pub
  • This command copies the SSH public key into the archive ~/.ssh/authorized_keys On the remote server, allow access to the user using the created key

Now try to access the server using your SSH private key located in the same directory as the public key.

ssh -p 22 -i ~/.ssh/sshkey newuser@serverip
Enter full screen mode

Exit full screen mode

  • -p specifies the SSH port defined on the server
  • -i ~/.ssh/sshkey specifies the private key path

If access using SSH keys works, we can remove SSH password authentication.

First open the SSH configuration file

sudo nano /etc/ssh/sshd_config
Enter full screen mode

Exit full screen mode

Find and change the following lines in the file

## HARDENING SSH CONECTION
PermitRootLogin no ## BLOQUEIA O LOGIN DIRETO COM ROOT
PasswordAuthentication no ## DESATIVA CONEXÃO POR SENHA
UsePAM no ## DESATIVA O USO DE PAM NO SSH 
Enter full screen mode

Exit full screen mode

After changing these lines, save and close the profile and restart the SSH service:

sudo systemctl reload ssh
Enter full screen mode

Exit full screen mode

To verify that the changes are valid, try connecting

root@serverip # a resposta deve ser essa root@serverip Permission denied (publickey).
Enter full screen mode

Exit full screen mode

Use SSH to connect to the new user.


6. Install docker and docker-compose

To configure the environment, follow the official documentation to install Docker and Docker Compose:

https://docs.docker.com/engine/install/ubuntu/

https://docs.docker.com/compose/install/linux/

After installing Docker, execute the image to verify that it is functioning properly.

docker run -p 80:80 -d nginxdemos/hello
Enter full screen mode

Exit full screen mode

sudo systemctl enable docker 
Enter full screen mode

Exit full screen mode

sudo usermod -aG docker newuser
Enter full screen mode

Exit full screen mode


7. Configure firewall

sudo ufw default deny incoming
Enter full screen mode

Exit full screen mode

sudo ufw default allow outgoing
Enter full screen mode

Exit full screen mode

sudo ufw allow OpenSSH
Enter full screen mode

Exit full screen mode

sudo ufw allow 80
Enter full screen mode

Exit full screen mode

sudo ufw allow 443
Enter full screen mode

Exit full screen mode

To make Docker comply with UFW rules, edit the Docker configuration file

sudo nano /etc/docker/daemon.json
Enter full screen mode

Exit full screen mode

{
  "iptables": false
}
Enter full screen mode

Exit full screen mode

Restart docker

sudo systemctl restart docker
Enter full screen mode

Exit full screen mode


8. Docker file configuration of the project

Create file Dockerfile Create a docker image in the root directory of the project

# Stage 1: Construção da aplicação usando Node.js
FROM node:22 AS build

# Define o diretório de trabalho dentro do container para organizar os arquivos
WORKDIR /app

# Copia os arquivos de configuração do Node.js (package.json e package-lock.json) para o container
COPY package*.json ./

# Instala as dependências do projeto
RUN npm install

# Copia o restante dos arquivos do projeto para o container
COPY . .

# Executa o comando para construir a aplicação (adapte este comando conforme o projeto)
RUN npm run build

# Stage 2: Configuração do servidor Nginx para servir a aplicação
FROM nginx:alpine

# Define o diretório de trabalho do Nginx onde os arquivos estáticos serão armazenados
WORKDIR /usr/share/nginx/html

# Copia os arquivos da build gerada no estágio anterior para o diretório do Nginx
COPY --from=build /app/dist/codebyfernandes/browser .

# Copia o arquivo de configuração personalizado do Nginx (opcional, se necessário)
COPY default.conf /etc/nginx/conf.d/default.conf

# Expõe a porta 80 para o servidor
EXPOSE 80

# Define o comando padrão para iniciar o Nginx
CMD ["nginx", "-g", "daemon off;"]

Enter full screen mode

Exit full screen mode

Create a default.conf file in the project root directory:

server {
    listen 80; # Porta onde o servidor vai escutar
    server_name localhost; # Nome do servidor (pode ser substituído pelo domínio)
    root /usr/share/nginx/html; # Diretório onde estão os arquivos da aplicação
    index index.html; # Arquivo principal da aplicação

    # Rota principal para servir o Angular
    location / {
        try_files $uri $uri/ /index.html; # Redireciona todas as rotas para o index.html
    }

    # Configuração de cache para arquivos estáticos
    location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|webmanifest)$ {
        expires 6M; # Define o tempo de expiração para 6 meses
        access_log off; # Desativa logs para esses arquivos
        add_header Cache-Control "public"; # Permite cache público
    }

    error_page 404 /index.html; # Redireciona erros 404 para o index.html

    # Logs personalizados
    error_log /var/log/nginx/angular-error.log; # Registro de erros
    access_log /var/log/nginx/angular-access.log; # Registro de acessos
}

Enter full screen mode

Exit full screen mode

Now create the image locally and run it afterwards

docker build -t meu-app:1.0 .
Enter full screen mode

Exit full screen mode

docker run -p 80:80 -d meu-app:1.0
Enter full screen mode

Exit full screen mode

  • d :Run the container in the background.
  • p 80:80: Map port 80 of the container to port 80 of the host.

If everything is configured correctly, you will be able to access your project from: http://localhost

Now push your image to Docker Hub using the following file:

Docker Hub: Build and push the first image


9. Docker compose configuration

Create the compose.yaml file in the root directory of the project. First, let us add the image of the web application

services:
  # Define os serviços do projeto
  webapp: 
    # Nome do serviço
    image: fernandeeess/portfolio-app:prod
    # Imagem do contêiner usada para o serviço (versão de produção)

Enter full screen mode

Exit full screen mode

Now let us configure the reverse proxy for your project using the following command transportationsimplifying management by automatically setting up SSL certificates and adjusting load balancers based on the services created.

services:
  reverse-proxy:
    # Define o serviço do reverse proxy
    image: traefik:v3.1
    # Especifica a imagem do Traefik que será usada
    command:
      # Ativa o provedor Docker
      - "--providers.docker"
      # Desativa a exposição automática de serviços no Traefik
      - "--providers.docker.exposedbydefault=false"
      # Configura a porta para HTTPS (websecure)
      - "--entryPoints.websecure.address=:443"
      # Configura o desafio TLS para emissão do certificado SSL
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      # Define o e-mail para registrar os certificados SSL
      - "--certificatesresolvers.myresolver.acme.email=youremail"
      # Define o local para armazenar os certificados SSL
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      # Configura a porta para HTTP (web)
      - "--entrypoints.web.address=:80"
      # Redireciona HTTP para HTTPS
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    ports:
      # Mapeia as portas do host para o contêiner (HTTP e HTTPS)
      - "80:80"
      - "443:443"
    volumes:
      # Volume para armazenar certificados SSL
      - letsencrypt:/letsencrypt
      # Permite que o Traefik se comunique com o Docker para detectar serviços
      - /var/run/docker.sock:/var/run/docker.sock

  webapp:
    # Define o serviço da aplicação web
    image: fernandeeess/portfolio-app:prod
    # Especifica a imagem do contêiner da aplicação

volumes:
  letsencrypt:
    # Volume para armazenar os dados do Let's Encrypt
Enter full screen mode

Exit full screen mode

Add as label to Web application service

webapp:
    # Define o serviço da aplicação web
    image: fernandeeess/portfolio-app:prod
    # Especifica a imagem do contêiner que será usada para o serviço
    labels:
      # Habilita o Traefik para este serviço
      - "traefik.enable=true"
      # Define a regra de roteamento baseada no domínio
      - "traefik.http.routers.webapp.rule=Host(`yourdomain.com`)"
      # Define o entrypoint como HTTPS (websecure)
      - "traefik.http.routers.webapp.entrypoints=websecure"
      # Configura o resolver de certificado SSL a ser usado
      - "traefik.http.routers.webapp.tls.certresolver=myresolver"
      # Define a porta interna do contêiner que será usada pelo load balancer
      - "traefik.http.services.webapp.loadbalancer.server.port=80"
    deploy:
      # Configuração de implantação do serviço
      mode: replicated
      # Define o número de réplicas (instâncias) do serviço
      replicas: 3
Enter full screen mode

Exit full screen mode

Configure the Watchtower service to monitor tagged Docker images prod. Whenever there is an update, Watchtower automatically pulls the changes and restarts the service.


# Continuous Delivery/Deployment
watchtower:
  image: containrrr/watchtower  # Imagem do Watchtower.
  command:
    - "--label-enable"        # Monitora apenas serviços com labels específicas.
    - "--interval"            # Define o intervalo de verificação.
    - "30"                    # Verifica atualizações a cada 30 segundos.
    - "--rolling-restart"     # Reinicia os serviços de forma gradual.
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock  # Permite ao Watchtower gerenciar containers.

# Adicione esta label ao serviço "webapp" para que o Watchtower o monitore:
# - "com.centurylinklabs.watchtower.enable=true"
Enter full screen mode

Exit full screen mode

The final file should look like this:

services:
  watchtower:
    image: containrrr/watchtower
    command:
      - "--label-enable"
      - "--interval"
      - "30"
      - "--rolling-restart"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  reverse-proxy:
    image: traefik:v3.1
    command:
      - "--providers.docker"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=email@email.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock

  webapp:
    image: fernandeeess/portfolio-app:prod
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webapp.rule=Host(`yourdomain.com`)"
      - "traefik.http.routers.webapp.entrypoints=websecure"
      - "traefik.http.routers.webapp.tls.certresolver=myresolver"
      - "traefik.http.services.webapp.loadbalancer.server.port=80"
      - "com.centurylinklabs.watchtower.enable=true"
    deploy:
      mode: replicated
      replicas: 3
volumes:
  letsencrypt:
Enter full screen mode

Exit full screen mode

If everything is configured correctly, you will be able to access your project on the configured domain


10. Set up CI using GitHub Actions

Automate the process of building and pushing Docker images to Docker Hub. Please follow the instructions provided at the link below:

Use GitHub Actions to automatically build Docker images and push them to Docker Hub

Remember, the image must be tagged “prod” so that Watchtower can pull the changes and restart the service.


11. Links

  • folder
  • Docker installation and configuration:

  • Docker files and concepts:

  • Additional resources and related tools:

  • Firewalls and Security Practices:

2024-12-22 21:45:23

Leave a Reply

Your email address will not be published. Required fields are marked *