diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da9b4dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Nunca sobe projetos NestJS — ficam só no VPS +projects/ +node_modules/ +*.log +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cea2b89 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install system deps +RUN apk add --no-cache \ + nginx \ + git \ + bash \ + curl \ + openssl + +# Install PM2 globally +RUN npm install -g pm2 + +# Create directories +RUN mkdir -p /home/deploy/projects \ + /home/deploy/scripts \ + /etc/nginx/sites-dynamic \ + /var/log/nginx \ + /run/nginx + +# Copy scripts +COPY scripts/ /home/deploy/scripts/ +RUN chmod +x /home/deploy/scripts/*.sh + +# Copy nginx base config +COPY nginx/nginx.conf /etc/nginx/nginx.conf + +WORKDIR /home/deploy + +EXPOSE 80 + +CMD ["/home/deploy/scripts/start.sh"] diff --git a/README.md b/README.md index 0fe7e3b..70bb2ef 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,135 @@ -# docker-nest-js +# NestJS Multi-Projeto Stack — Portainer +## Estrutura no Host (VPS) + +``` +/home/deploy/ +├── nest-stack/ ← este repositório +│ ├── Dockerfile +│ ├── docker-compose.yml +│ ├── nginx/nginx.conf +│ ├── scripts/ +│ │ ├── start.sh ← entrypoint do container +│ │ └── deploy.sh ← deploy dentro do container +│ └── host-deploy.sh ← atalho para rodar do host +│ +└── projects/ ← SEUS PROJETOS (volume montado) + ├── projeto1/ ← git clone aqui + └── projeto2/ +``` + +--- + +## Setup inicial + +### 1. No servidor (VPS), clone este repo + +```bash +git clone /home/deploy/nest-stack +mkdir -p /home/deploy/projects +chmod +x /home/deploy/nest-stack/host-deploy.sh +``` + +### 2. No Portainer → Stacks → Add Stack + +- **Name:** `nestjs-stack` +- **Build method:** Repository (aponta para este repo) + — OU — +- **Build method:** Upload → sobe o `docker-compose.yml` + +### 3. Clique em **Deploy the stack** + +O container vai: +- Buildar a imagem (Nginx + Node + PM2) +- Montar `/home/deploy/projects` como volume +- Subir projetos que já tiverem build em `dist/` + +--- + +## Adicionando um novo projeto + +```bash +# No HOST (fora do container) +cd /home/deploy/projects +git clone https://github.com/seu-usuario/meu-projeto.git projeto1 + +# Roda o deploy +./home/deploy/nest-stack/host-deploy.sh projeto1 + +# Pronto! Acessível em: +# http://nest.juancjc.com.br/projeto1 +``` + +--- + +## Atualizando um projeto existente + +```bash +# Atualiza o código +cd /home/deploy/projects/projeto1 +git pull + +# Redeploya +/home/deploy/nest-stack/host-deploy.sh projeto1 +``` + +--- + +## Reset do container (via Portainer) + +1. Portainer → Containers → `nestjs-stack` → **Restart** ou **Reset** +2. O container sobe e **automaticamente relança todos os projetos** que tiverem `dist/main.js` +3. Os arquivos dos projetos continuam intactos (estão no volume do host) + +--- + +## Comandos úteis + +```bash +# Ver logs do container +docker logs nestjs-stack -f + +# Ver processos PM2 rodando +docker exec nestjs-stack pm2 list + +# Ver logs de um projeto específico +docker exec nestjs-stack pm2 logs projeto1 + +# Entrar no container +docker exec -it nestjs-stack bash + +# Ver status do nginx +docker exec nestjs-stack nginx -t +``` + +--- + +## Regras de porta + +| Projeto | Porta interna | +|---------------|---------------| +| projeto1 | 3001 | +| projeto2 | 3002 | +| projeto3 | 3003 | +| projetoN | 300N | + +A porta é determinada pela **ordem alfabética das pastas** em `/home/deploy/projects`. + +--- + +## Seu projeto NestJS não precisa de nenhuma config especial + +O Nginx remove o prefixo `/projeto1` antes de repassar ao NestJS. +Seu app recebe as rotas normalmente como `/`. + +**Único requisito:** seu `main.ts` deve aceitar a porta via variável de ambiente ou argumento: + +```typescript +// main.ts +async function bootstrap() { + const app = await NestFactory.create(AppModule); + const port = process.argv.find((_, i, a) => a[i-1] === '--port') || process.env.PORT || 3000; + await app.listen(port); +} +bootstrap(); +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2742369 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3.8" + +services: + nest-stack: + build: + context: . + dockerfile: Dockerfile + container_name: nestjs-stack + restart: unless-stopped + ports: + - "2442:80" + volumes: + # Projetos ficam no host — persistem no reset do container + - /home/deploy/projects:/home/deploy/projects + # Scripts acessíveis para executar deploy de fora do container + - ./scripts:/home/deploy/scripts + environment: + - PROJECTS_DIR=/home/deploy/projects + - BASE_PORT=3000 + - DOMAIN=nest.juancjc.com.br + labels: + - "com.neststack.managed=true" + +# Nota: sem volume nomeado para projects — pasta no host é intencional +# Isso permite git clone direto no host sem entrar no container diff --git a/host-deploy.sh b/host-deploy.sh new file mode 100644 index 0000000..ec0874b --- /dev/null +++ b/host-deploy.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# host-deploy.sh — rode isso NO HOST para deployar um projeto +# Uso: ./host-deploy.sh meu-projeto +# Ou: ./host-deploy.sh (deploya todos) + +TARGET="${1:-}" + +if [ -n "$TARGET" ]; then + echo ">>> Deployando $TARGET via container..." + docker exec nestjs-stack /home/deploy/scripts/deploy.sh "$TARGET" +else + echo ">>> Deployando todos os projetos via container..." + docker exec nestjs-stack /home/deploy/scripts/deploy.sh +fi diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..fe85966 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,36 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + client_max_body_size 50M; + + server { + listen 80; + server_name _; + + # Rota raiz — lista projetos disponíveis + location = / { + return 200 'NestJS Stack — use /nome-do-projeto para acessar seus projetos'; + add_header Content-Type text/plain; + } + + # Rotas dinâmicas dos projetos (geradas pelo gen-nginx.sh) + include /etc/nginx/sites-dynamic/*.conf; + } +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..0a990f8 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# deploy.sh — deploya um projeto ou todos +# Uso dentro do container: +# /home/deploy/scripts/deploy.sh → deploya todos +# /home/deploy/scripts/deploy.sh meu-projeto → deploya só um + +PROJECTS_DIR="${PROJECTS_DIR:-/home/deploy/projects}" +BASE_PORT="${BASE_PORT:-3000}" +TARGET="${1:-}" + +deploy_project() { + local project="$1" + local port="$2" + local project_dir="$PROJECTS_DIR/$project" + + echo "" + echo ">>> Deployando: $project → porta $port" + + if [ ! -d "$project_dir" ]; then + echo "✗ Pasta $project_dir não encontrada" + return 1 + fi + + cd "$project_dir" + + # Instala dependências + echo " → npm install..." + npm install --prefer-offline 2>&1 | tail -3 + + # Build + echo " → npm run build..." + npm run build + if [ $? -ne 0 ]; then + echo " ✗ Build falhou para $project" + return 1 + fi + + # Salva a porta usada + echo "$port" > "$project_dir/.port" + + # PM2: reinicia se já existe, senão cria + if pm2 describe "$project" > /dev/null 2>&1; then + echo " → Reiniciando processo PM2..." + pm2 restart "$project" + else + echo " → Criando processo PM2..." + pm2 start dist/main.js \ + --name "$project" \ + --env production \ + -- --port "$port" + fi + + # Gera/atualiza config nginx + cat > "/etc/nginx/sites-dynamic/${project}.conf" </dev/null || echo "?") + echo " ✓ /$project → localhost:$port" +done +echo "================================================" diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..11cdab4 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# start.sh — entrypoint do container +# Roda ao iniciar/resetar o container + +PROJECTS_DIR="${PROJECTS_DIR:-/home/deploy/projects}" +BASE_PORT="${BASE_PORT:-3000}" + +echo "================================================" +echo " NestJS Stack — Iniciando..." +echo "================================================" + +# Garante que a pasta de projetos existe +mkdir -p "$PROJECTS_DIR" +mkdir -p /etc/nginx/sites-dynamic + +# Limpa configs antigas do nginx (container pode ter sido resetado) +rm -f /etc/nginx/sites-dynamic/*.conf + +# Inicia nginx +echo "→ Iniciando Nginx..." +nginx -t && nginx +echo "✓ Nginx rodando" + +# Se houver projetos com build existente, sobe eles via PM2 +echo "→ Verificando projetos existentes..." +port_index=1 + +for project_dir in "$PROJECTS_DIR"/*/; do + [ -d "$project_dir" ] || continue + project=$(basename "$project_dir") + port=$((BASE_PORT + port_index)) + + # Só sobe se já tiver build + if [ -f "$project_dir/dist/main.js" ]; then + echo " → Subindo $project na porta $port" + + pm2 start "$project_dir/dist/main.js" \ + --name "$project" \ + --env production \ + -- --port "$port" 2>/dev/null + + # Gera config nginx para esse projeto + cat > "/etc/nginx/sites-dynamic/${project}.conf" < "$project_dir/.port" + else + echo " ⚠ $project sem build — rode: docker exec nestjs-stack /home/deploy/scripts/deploy.sh $project" + fi + + port_index=$((port_index + 1)) +done + +# Recarrega nginx com as configs dos projetos +nginx -s reload 2>/dev/null || true +echo "✓ Nginx atualizado com rotas dos projetos" + +pm2 save 2>/dev/null || true + +echo "" +echo "================================================" +echo " Stack pronta!" +echo " Acesse: http://\${DOMAIN:-localhost}/" +echo "================================================" + +# Mantém o container vivo e faz log do PM2 +pm2 logs --raw