# Instalação do Laravel com Docker

## Pré-requisitos

- Docker instalado
- Docker Compose instalado
- Estrutura com `infra/` (Docker/Nginx) e `app/` (Laravel)

---

## Ambientes

Este projeto possui dois ambientes separados:

| Ambiente | Comando (na pasta `infra/`) | Dockerfile | Nginx |
|----------|-----------------------------|------------|-------|
| Desenvolvimento | `docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d` | `infra/Dockerfile.dev` | `nginx/default.dev.conf` |
| Produção | `docker compose -f docker-compose.yml up -d` | `infra/Dockerfile` | `nginx/default.conf` |

> **Build de produção (`infra/Dockerfile`):** o stage *builder* copia `app/composer.json` e `app/composer.lock`. Só faça o build da imagem de produção **depois** de o Laravel existir em `app/` (por exemplo com `composer create-project`). Com `app/` sem o projeto, o `COPY` falha — comportamento esperado.

---

## Instalação em desenvolvimento

### Passo 0 — Arquivo de ambiente do Laravel (`app/.env`)

Antes do primeiro `docker compose`, o MySQL e o Redis leem `../app/.env`. Se ainda não instalou o Laravel, copie o modelo:

```bash
cp infra/.env.example app/.env
```

Depois do `composer create-project`, o Laravel gera `app/.env`; mantenha as variáveis `DB_*`, `MYSQL_*`, `REDIS_*` e drivers alinhados com este exemplo (a imagem MySQL usa `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD`).

### Passo 1 — Sobe MySQL e Redis

```bash
cd infra
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d mysql redis
```

Aguarde os healthchecks passarem:

```bash
docker compose ps
```

Os serviços `mysql` e `redis` devem aparecer como `healthy`.

---

### Passo 2 — Instala o Laravel via Composer

Na **raiz do repositório** (pasta que contém `app/` e `infra/`):

> **Atenção:** o Composer exige que a pasta `app/` esteja **completamente vazia**.  
> Se você criou o `app/.env` no Passo 0, mova-o temporariamente para a raiz do repositório, rode o comando abaixo e depois mova de volta para `app/.env`.

```bash
docker run --rm \
  -v "$(pwd)/app:/app" \
  -w /app \
  composer:2 create-project laravel/laravel . --prefer-dist
```

> Instala o código em `app/`. No Passo 3, alinhe o `app/.env` com `DB_HOST=mysql`, `REDIS_HOST=redis`, etc. (use o `infra/.env.example` como referência).

---

### Passo 3 — Ajusta o `app/.env`

O Laravel cria o `.env` em `app/`. Garanta (como no `infra/.env.example`):

```env
APP_URL=http://localhost:8080

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel
DB_PASSWORD=laravel_password

REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_password

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
```

---

### Passo 4 — Sobe todos os containers

```bash
cd infra
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
```

---

### Passo 5 — Gera a chave da aplicação

```bash
cd infra
docker compose exec app php artisan key:generate
```

---

### Passo 6 — Roda as migrations

```bash
docker compose exec app php artisan migrate
```

---

### Passo 7 — Ajusta as permissões

```bash
docker compose exec app chown -R www-data:www-data /var/www/html/storage
docker compose exec app chown -R www-data:www-data /var/www/html/bootstrap/cache
```

> Se estiver no **Linux** e tiver problemas para editar arquivos via VS Code (bind mount com dono `www-data` no container), rode **na máquina hospedeira** (na raiz do repositório):  
> `sudo chown -R $USER:$USER app/`

---

### Passo 8 — Acessa a aplicação

Abra no navegador:

```
http://localhost:8080
```

---

## Usando banco externo (RDS ou SQL Server)

### RDS MySQL ou RDS PostgreSQL

Remova o serviço `mysql` do `infra/docker-compose.yml` e atualize o `app/.env`:

```env
# RDS MySQL
DB_CONNECTION=mysql
DB_HOST=seu-rds.us-east-1.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=seu_banco
DB_USERNAME=seu_user
DB_PASSWORD=sua_senha
```

```env
# RDS PostgreSQL
DB_CONNECTION=pgsql
DB_HOST=seu-rds.us-east-1.rds.amazonaws.com
DB_PORT=5432
DB_DATABASE=seu_banco
DB_USERNAME=seu_user
DB_PASSWORD=sua_senha
```

> Para PostgreSQL, adicione `pdo_pgsql` no `infra/Dockerfile`:
> ```dockerfile
> && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd pdo_pgsql \
> ```
> Depois rode `docker compose build --no-cache app` (na pasta `infra/`).

---

### SQL Server (Azure / AWS RDS)

No `infra/Dockerfile`, adicione após o bloco de extensões PHP:

```dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends \
    unixodbc-dev \
    && pecl install sqlsrv pdo_sqlsrv \
    && docker-php-ext-enable sqlsrv pdo_sqlsrv \
    && apt-get clean && rm -rf /var/lib/apt/lists/*
```

Atualize o `.env`:

```env
DB_CONNECTION=sqlsrv
DB_HOST=seu-servidor.database.windows.net
DB_PORT=1433
DB_DATABASE=seu_banco
DB_USERNAME=seu_user
DB_PASSWORD=sua_senha
```

Depois rebuild:

```bash
cd infra
docker compose build --no-cache app
docker compose up -d
```

---


## CI/CD

Escolha a plataforma e o modo que preferir. Todas as opções fazem a mesma coisa — a diferença é só quando o deploy dispara.

---

### GitHub Actions — automático

Deploy dispara sozinho ao mergear na `main`.

Crie o arquivo `.github/workflows/deploy.yml`:

```yaml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build imagem
        run: docker build -f infra/Dockerfile -t laravel_app .

      - name: Login Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push imagem
        run: |
          docker tag laravel_app ${{ secrets.DOCKER_USERNAME }}/laravel_app:latest
          docker push ${{ secrets.DOCKER_USERNAME }}/laravel_app:latest

      - name: Deploy no servidor
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/meu-projeto/infra
            docker compose pull
            docker compose up -d

            echo "Aguardando app ficar pronto..."
            until docker compose exec -T app php artisan --version > /dev/null 2>&1; do
              echo "  ainda subindo..."
              sleep 3
            done

            docker compose exec -T app php artisan migrate --force
            docker compose exec -T app php artisan config:cache
            docker compose exec -T app php artisan route:cache
            docker compose exec -T app php artisan view:cache
```

---

### GitHub Actions — manual

Deploy dispara só quando você clicar em "Run workflow" lá no GitHub (`Actions → Deploy → Run workflow`).

Crie o arquivo `.github/workflows/deploy.yml`:

```yaml
name: Deploy

on:
  workflow_dispatch:    # disparo manual pelo GitHub

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build imagem
        run: docker build -f infra/Dockerfile -t laravel_app .

      - name: Login Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push imagem
        run: |
          docker tag laravel_app ${{ secrets.DOCKER_USERNAME }}/laravel_app:latest
          docker push ${{ secrets.DOCKER_USERNAME }}/laravel_app:latest

      - name: Deploy no servidor
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/meu-projeto/infra
            docker compose pull
            docker compose up -d

            echo "Aguardando app ficar pronto..."
            until docker compose exec -T app php artisan --version > /dev/null 2>&1; do
              echo "  ainda subindo..."
              sleep 3
            done

            docker compose exec -T app php artisan migrate --force
            docker compose exec -T app php artisan config:cache
            docker compose exec -T app php artisan route:cache
            docker compose exec -T app php artisan view:cache
```

Secrets necessários no GitHub (`Settings → Secrets → Actions`):

| Secret | Descrição |
|--------|-----------|
| `DOCKER_USERNAME` | Usuário do Docker Hub |
| `DOCKER_PASSWORD` | Senha do Docker Hub |
| `SERVER_HOST` | IP ou domínio do servidor |
| `SERVER_USER` | Usuário SSH do servidor |
| `SERVER_SSH_KEY` | Chave SSH privada |

> **Atenção:** a imagem Docker gerada contém o **código-fonte** da aplicação. Se o repositório de código for privado, use repositório de imagem **privado** no Docker Hub (ou outro registry) e controle quem tem `docker pull`.

---

### GitLab CI/CD — automático

Deploy dispara sozinho ao fazer push na branch `main`.

Crie o arquivo `.gitlab-ci.yml` na raiz do projeto:

```yaml
stages:
  - build
  - deploy

variables:
  IMAGE_NAME: $CI_REGISTRY_USER/laravel_app

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
  script:
    - docker build -f infra/Dockerfile -t $IMAGE_NAME:latest .
    - docker push $IMAGE_NAME:latest
  only:
    - main

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SERVER_SSH_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
  script:
    - ssh $SERVER_USER@$SERVER_HOST "
        cd /var/www/meu-projeto/infra &&
        docker compose pull &&
        docker compose up -d &&
        echo 'Aguardando app ficar pronto...' &&
        until docker compose exec -T app php artisan --version > /dev/null 2>&1; do echo '  ainda subindo...'; sleep 3; done &&
        docker compose exec -T app php artisan migrate --force &&
        docker compose exec -T app php artisan config:cache &&
        docker compose exec -T app php artisan route:cache &&
        docker compose exec -T app php artisan view:cache
      "
  only:
    - main
```

---

### GitLab CI/CD — manual

Deploy dispara só quando você clicar em "play" lá no GitLab (`CI/CD → Pipelines → play no job deploy`).

Crie o arquivo `.gitlab-ci.yml` na raiz do projeto:

```yaml
stages:
  - build
  - deploy

variables:
  IMAGE_NAME: $CI_REGISTRY_USER/laravel_app

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
  script:
    - docker build -f infra/Dockerfile -t $IMAGE_NAME:latest .
    - docker push $IMAGE_NAME:latest
  only:
    - main

deploy:
  stage: deploy
  image: alpine:latest
  when: manual        # disparo manual
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SERVER_SSH_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
  script:
    - ssh $SERVER_USER@$SERVER_HOST "
        cd /var/www/meu-projeto/infra &&
        docker compose pull &&
        docker compose up -d &&
        echo 'Aguardando app ficar pronto...' &&
        until docker compose exec -T app php artisan --version > /dev/null 2>&1; do echo '  ainda subindo...'; sleep 3; done &&
        docker compose exec -T app php artisan migrate --force &&
        docker compose exec -T app php artisan config:cache &&
        docker compose exec -T app php artisan route:cache &&
        docker compose exec -T app php artisan view:cache
      "
  only:
    - main
```

Variáveis necessárias no GitLab (`Settings → CI/CD → Variables`):

| Variável | Descrição |
|----------|-----------|
| `CI_REGISTRY_USER` | Usuário do Docker Hub |
| `CI_REGISTRY_PASSWORD` | Senha do Docker Hub |
| `SERVER_HOST` | IP ou domínio do servidor |
| `SERVER_USER` | Usuário SSH do servidor |
| `SERVER_SSH_KEY` | Chave SSH privada |

> **Atenção:** a imagem Docker gerada contém o **código-fonte** da aplicação. Se o repositório de código for privado, use repositório de imagem **privado** no Docker Hub (ou outro registry) e controle quem tem `docker pull`.

---

## HTTPS com Let's Encrypt (Certbot)

Em produção, use Certbot para gerar certificados gratuitos. No servidor:

### Passo 1 — Instala o Certbot

```bash
apt install certbot python3-certbot-nginx -y
```

### Passo 2 — Gera o certificado

```bash
certbot certonly --standalone -d seu-dominio.com.br
```

Os certificados são gerados em:
```
/etc/letsencrypt/live/seu-dominio.com.br/fullchain.pem
/etc/letsencrypt/live/seu-dominio.com.br/privkey.pem
```

> **Atenção:** o Nginx só sobe se os arquivos `cert.pem` e `key.pem` já existirem nos caminhos mapeados (ex.: `./nginx/certs/`). Garanta que o Certbot (ou outra origem) tenha gerado/copiado os certificados **antes** de subir o stack com os volumes de SSL no `docker-compose.yml`.

### Passo 3 — Mapeia os certificados no `infra/docker-compose.yml`

```yaml
nginx:
  volumes:
    - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    - /etc/letsencrypt/live/seu-dominio.com.br/fullchain.pem:/etc/nginx/certs/cert.pem:ro
    - /etc/letsencrypt/live/seu-dominio.com.br/privkey.pem:/etc/nginx/certs/key.pem:ro
```

### Passo 4 — Renovação automática

O Certbot já instala um cron de renovação automática. Para testar:

```bash
certbot renew --dry-run
```

---

## Monitoramento

### Opção simples — healthcheck endpoint

Adicione em `routes/web.php`:

```php
Route::get('/health', function () {
    return response()->json([
        'status' => 'ok',
        'timestamp' => now(),
    ]);
});
```

Monitore com UptimeRobot ou Better Uptime (ambos gratuitos) apontando para `https://seu-dominio.com.br/health`.

---

### Opção avançada — Prometheus + Grafana

Adicione ao `infra/docker-compose.yml` (e crie `infra/docker/prometheus/prometheus.yml`):

```yaml
  prometheus:
    image: prom/prometheus:latest
    container_name: laravel_prometheus
    volumes:
      - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "127.0.0.1:9090:9090"
    networks:
      - laravel_network

  grafana:
    image: grafana/grafana:latest
    container_name: laravel_grafana
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - laravel_network

volumes:
  grafana_data:
```

Crie `docker/prometheus/prometheus.yml`:

```yaml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'laravel'
    static_configs:
      - targets: ['app:9000']
```

Acesse o Grafana em `http://localhost:3000` (usuário: `admin`, senha: `admin`).

---

## Comandos úteis do dia a dia

| Ação | Comando (com `cd infra`) |
|------|--------------------------|
| Ver logs | `docker compose logs -f` |
| Ver logs de um serviço | `docker compose logs -f app` |
| Entrar no container | `docker compose exec app bash` |
| Rodar artisan | `docker compose exec app php artisan <comando>` |
| Parar tudo | `docker compose down` |
| Parar e apagar volumes | `docker compose down -v` |
| Rebuild da imagem | `docker compose build --no-cache app` |
| Limpar todos os caches | `docker compose exec app php artisan optimize:clear` |

---

## Troubleshooting

### Container `app` não sobe

Verifique os logs:
```bash
docker compose logs app
```

Causas comuns:
- MySQL ou Redis ainda não estão `healthy` — aguarde e tente novamente
- Erro de permissão no `storage/` — rode:
```bash
docker compose exec app chmod -R 775 /var/www/html/storage
docker compose exec app chmod -R 775 /var/www/html/bootstrap/cache
```

---

### Erro 502 Bad Gateway no Nginx

O Nginx não consegue se comunicar com o PHP-FPM. Verifique:
```bash
docker compose logs nginx
docker compose logs app
```

Causas comuns:
- Container `app` não está rodando — `docker compose up -d app`
- `fastcgi_pass app:9000` com nome errado — confirma que o serviço no `infra/docker-compose.yml` se chama `app`

---

### Erro de permissão no `storage/`

```bash
docker compose exec app bash -c "chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache"
docker compose exec app bash -c "chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache"
```

---

### MySQL não conecta

Verifique se o healthcheck passou:
```bash
docker compose ps
```

Se aparecer `starting` ao invés de `healthy`, aguarde ou verifique os logs:
```bash
docker compose logs mysql
```

Confirme que `app/.env` contém `DB_*`, `MYSQL_*` e `REDIS_PASSWORD` coerentes com o que os serviços esperam.

---

### Limpar tudo e recomeçar

```bash
docker compose down -v        # para containers e apaga volumes
docker compose build --no-cache app  # rebuild da imagem
docker compose up -d          # sobe tudo do zero
```

> **Atenção:** `down -v` apaga os dados do banco. Use só em ambiente local.

---

## Estrutura do repositório

- **`app/`** — código Laravel (`.env` aqui, não versionado; `composer create-project` cria `public/`, `storage/`, etc.).
- **`infra/`** — Docker, Nginx e documentação; subir os containers sempre a partir de `infra/` (ou use `-f infra/docker-compose.yml` na raiz).

O **contexto de build** da imagem é a **raiz do repositório**; o arquivo **`.dockerignore`** fica na raiz (ao lado de `app/` e `infra/`), não dentro de `infra/`.

```
meu-projeto/
├── .dockerignore
├── .gitignore
├── app/
│   ├── .env              ← não versionado (Laravel + variáveis usadas pelo Compose)
│   └── .gitignore
└── infra/
    ├── docker/
    │   └── php/
    │       ├── opcache.ini
    │       ├── opcache.dev.ini
    │       ├── php.ini
    │       └── xdebug.ini
    ├── nginx/
    │   ├── certs/
    │   │   └── .gitkeep
    │   ├── default.conf
    │   └── default.dev.conf
    ├── docker-compose.yml
    ├── docker-compose.dev.yml
    ├── Dockerfile
    ├── Dockerfile.dev
    ├── .env.example        ← modelo para copiar em ../app/.env antes do primeiro up
    └── INSTALACAO.md
```

