DEV Community

Matheus Fernandes Rodrigues
Matheus Fernandes Rodrigues

Posted on

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

Optei por usar uma VPS principalmente pelo custo-benefício que ela proporciona. Com essa opção, tenho a chance de estudar e testar em um ambiente controlado, evitando surpresas financeiras no fim do mês. A flexibilidade para configurar e testar diversos cenários me permite replicar situações reais, facilitando o aprendizado e o desenvolvimento de habilidades de maneira prática e eficaz, tudo dentro de um orçamento previsível. Essa alternativa é perfeita para quem deseja explorar novas oportunidades sem gastar excessivamente.

1. Contrate um serviço de VPS para obter recursos dedicados.

Existem várias opções disponíveis, mas no meu caso, optei pelo serviço da Contabo por apenas 5 dólares mensais, oferecendo 4 cores de CPU, 6 GB de RAM, 400 GB de armazenamento SSD, 200 Mbit/s de velocidade de rede e 32 TB de tráfego mensal (com entrada ilimitada)

Realize o primeiro acesso ao servidor utilizando o protocolo SSH com o seguinte comando no terminal, utilizando o IP da máquina contratada:

ssh root@<IP-do-servidor>
Enter fullscreen mode Exit fullscreen mode

Encontre o IP da sua máquina para configurar o DNS utilizando o comando

ip addr
Enter fullscreen mode Exit fullscreen mode

Configure o DNS no serviço onde o domínio foi adquirido. Consulte a documentação oficial do provedor para orientações detalhadas sobre como realizar essa configuração.

2. Atualize os pacotes

sudo apt update && sudo apt upgrade -y
Enter fullscreen mode Exit fullscreen mode

3. Criando um Novo Usuário

Trabalhar diretamente com o usuário root pode expor seu servidor a riscos desnecessários, a melhor pratica e criar um novo usurário com privilégios limitados.

adduser newuser
Enter fullscreen mode Exit fullscreen mode

4. Concedendo permissões de sudo

Após a criação do usuário devemos adiciona-lo ao sudo group para que ele possa realizar comandos que exigem privilégios de nível elevado, sem a necessidade de utilizar diretamente o usuário root

usermod -aG sudo newuser
Enter fullscreen mode Exit fullscreen mode

Agora podemos trocar para o novo usuário para darmos inicio a configuração do sistema

su - newuser
Enter fullscreen mode Exit fullscreen mode

5. Removendo autenticação por senha do SSH

Para aumentar a segurança da conexão SSH, é recomendável desativar a autenticação por senha, isso elimina o risco de ataques de brute force baseado em senha.

Antes de desativarmos a autenticação de senha para o SSH, devemos criar um par de chaves SSH para garantir que ainda poderemos acessar o servidor

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 fullscreen mode Exit fullscreen mode

Após a criação da chave devemos adicionar a chave SSH ao servidor

ssh-copy-id -i ~/.ssh/sshkey.pub newuser@serverip
Enter fullscreen mode Exit fullscreen mode
  • indica o caminho onde a sua chave esta localizado localmente -i ~/.ssh/sshkey.pub
  • Esse comando copia a chave pública SSH para o arquivo ~/.ssh/authorized_keys no servidor remoto, permitindo o acesso para o usuário com a chave criada

Agora tente acessar o servidor utilizado a chave SSH privada localizada no mesmo diretório da chave publica

ssh -p 22 -i ~/.ssh/sshkey newuser@serverip
Enter fullscreen mode Exit fullscreen mode
  • -p especifica a porta SSH definida no servidor
  • -i ~/.ssh/sshkey especifica o caminho da chave privada

Se acesso utilizando a chave SSH der certo podemos remover a autenticação por senha do SSH.

Primeiro abra o arquivo de configuração SSH

sudo nano /etc/ssh/sshd_config
Enter fullscreen mode Exit fullscreen mode

Localize e altere as seguintes linhas no arquivo

## 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 fullscreen mode Exit fullscreen mode

após alterar essas linhas salve e feche o arquivo de configuração e reinicie o serviço de SSH:

sudo systemctl reload ssh
Enter fullscreen mode Exit fullscreen mode

para validar se as alterações estão funcionando tente se conectar

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

Conecte-se ao novo usuário utilizando o SSH.

6. Instale o docker e docker-compose

Para configurar o ambiente, prossiga com a instalação do Docker e do Docker Compose seguindo a documentação oficial:

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

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

Após instalar o Docker, execute esta imagem para verificar se ele está funcionando corretamente.

docker run -p 80:80 -d nginxdemos/hello
Enter fullscreen mode Exit fullscreen mode

Image description

sudo systemctl enable docker 
Enter fullscreen mode Exit fullscreen mode
sudo usermod -aG docker newuser
Enter fullscreen mode Exit fullscreen mode

7. Configurar Firewall

sudo ufw default deny incoming
Enter fullscreen mode Exit fullscreen mode
sudo ufw default allow outgoing
Enter fullscreen mode Exit fullscreen mode
sudo ufw allow OpenSSH
Enter fullscreen mode Exit fullscreen mode
sudo ufw allow 80
Enter fullscreen mode Exit fullscreen mode
sudo ufw allow 443
Enter fullscreen mode Exit fullscreen mode

Para que o Docker respeite as regras do UFW, edite o arquivo de configuração do Docker

sudo nano /etc/docker/daemon.json
Enter fullscreen mode Exit fullscreen mode
{
  "iptables": false
}
Enter fullscreen mode Exit fullscreen mode

Reinicie o docker

sudo systemctl restart docker
Enter fullscreen mode Exit fullscreen mode

8. Configuração do Docker file para o Projeto

Crie o arquivo Dockerfile na raiz do seu projeto para a criação da imagem docker

# 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 fullscreen mode Exit fullscreen mode

Crie o arquivo default.conf na raiz do projeto:

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 fullscreen mode Exit fullscreen mode

Agora crie a imagem localmente e rode ela em seguida

docker build -t meu-app:1.0 .
Enter fullscreen mode Exit fullscreen mode
docker run -p 80:80 -d meu-app:1.0
Enter fullscreen mode Exit fullscreen mode
  • d : Executa o container em segundo plano.
  • p 80:80: Mapeia a porta 80 do container para a porta 80 do host.

Se tudo estiver configurado corretamente, você poderá acessar seu projeto em: http://localhost

Agora envie sua imagem para o Docker Hub utilizando a documentação abaixo:

Docker Hub: Build and Push First Image

9. Configuração do docker compose

Crie o arquivo compose.yaml na raiz do seu projeto, primeiro vamos adicionar a imagem do seu web app

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 fullscreen mode Exit fullscreen mode

Agora vamos configurar o reverse proxy para o seu projeto utilizando o Traefik, que simplifica a gestão ao configurar automaticamente o certificado SSL e ajustar o load balancer de acordo com os serviços criados.

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 fullscreen mode Exit fullscreen mode

Adicione as labels ao web app 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 fullscreen mode Exit fullscreen mode

Configure o serviço do Watchtower para monitorar a imagem Docker com a tag prod. Sempre que houver uma atualização, o Watchtower irá puxar as alterações e reiniciar os serviços automaticamente.


# 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 fullscreen mode Exit fullscreen mode

O arquivo final deve ficar assim:

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 fullscreen mode Exit fullscreen mode

Se tudo estiver configurado corretamente, você poderá acessar seu projeto no seu domínio configurado

10. Configure o CI utilizando o GitHub Actions

Automatize o processo de construção da imagem Docker e envio ao Docker Hub. Siga o tutorial disponível no link abaixo:

Automate Docker Image Builds and Push to Docker Hub Using GitHub Actions

Lembre-se de que a imagem deve estar com a tag "prod" para que o Watchtower possa puxar as alterações e reiniciar os serviços.

11. Links

Top comments (0)