DEV Community

Cover image for Como realizar o Deploy de Projetos Web em uma VPS
Matheus Fernandes Rodrigues
Matheus Fernandes Rodrigues

Posted on

Como realizar o Deploy de Projetos Web em uma VPS

Escolhi utilizar uma VPS para o deploy do meu portfólio pelo excelente custo-benefício e controle de gastos. Essa solução permite testar configurações em um ambiente controlado, simulando cenários reais de forma prática e sem surpresas financeiras. Baseei-me no vídeo do canal Dreams of Code, que explica de forma clara como configurar uma VPS.

A seguir, descrevo o passo a passo que segui para configurar minha VPS e que você também poderá seguir para subir o seu projeto

cloud-architecture

1. Contrate um serviço de VPS para adquirir seu servidor dedicado.

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 núcleos 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),
rodando o Ubuntu Server 22.04.5 LTS

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

Adiciona o serviço docker à lista de serviços que são iniciados automaticamente na inicialização do sistema.

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

Adiciona o usuário newuser ao grupo docker, concedendo permissões para que ele possa executar comandos com o Docker sem a necessidade de utilizar sudo

7. Configurar Firewall

sudo ufw default deny incoming
Enter fullscreen mode Exit fullscreen mode

Bloqueia todas as conexões de entrada por padrão para maior segurança

sudo ufw default allow outgoing
Enter fullscreen mode Exit fullscreen mode

Permite todas as conexões de saída por padrão para permitir o tráfego de saída.

sudo ufw allow OpenSSH
Enter fullscreen mode Exit fullscreen mode

Permite conexões SSH (porta 22) para acesso remoto seguro ao servidor.

sudo ufw allow 80
Enter fullscreen mode Exit fullscreen mode

Permite conexões HTTP (porta 80) para acesso web não criptografado.

sudo ufw allow 443
Enter fullscreen mode Exit fullscreen mode

Permite conexões HTTPS (porta 443) para acesso web seguro e criptografado.

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:

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)