From 0004fa18956bc379675f9cabf4ef5030887e5780 Mon Sep 17 00:00:00 2001 From: Johan Date: Wed, 4 Feb 2026 11:52:22 +0100 Subject: [PATCH] docker --- .github/workflows/ci.yml | 87 +++++++++++++++++++++++++++++++++ atelier02-conteneurisation.pdf | Bin 0 -> 50968 bytes backend/Dockerfile | 26 ++++++++-- docker-compose.yml | 50 +++++++++++++++++++ frontend/Dockerfile | 42 ++++++++++++++++ 5 files changed, 201 insertions(+), 4 deletions(-) create mode 100755 atelier02-conteneurisation.pdf create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bf29fd..b8d767e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,3 +55,90 @@ jobs: - name: Run tests run: | if [ -d backend/tests ]; then pytest -q; else echo "No tests found"; fi + + docker-lint: + name: Docker Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run Hadolint on backend Dockerfile + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: backend/Dockerfile + ignore: DL3045 + + - name: Run Hadolint on frontend Dockerfile + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: frontend/Dockerfile + ignore: DL3045 + + build-and-scan: + name: Build and Scan Docker Images + needs: docker-lint + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build backend image + uses: docker/build-push-action@v5 + with: + context: ./backend + file: ./backend/Dockerfile + push: false + load: true + tags: backend:latest + + - name: Build frontend image + uses: docker/build-push-action@v5 + with: + context: ./frontend + file: ./frontend/Dockerfile + push: false + load: true + tags: frontend:latest + + - name: Scan backend with Trivy + uses: aquasecurity/trivy-action@master + with: + image-ref: backend:latest + format: sarif + output: backend-trivy.sarif + + - name: Scan frontend with Trivy + uses: aquasecurity/trivy-action@master + with: + image-ref: frontend:latest + format: sarif + output: frontend-trivy.sarif + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: backend-trivy.sarif + + - name: Upload frontend Trivy results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: frontend-trivy.sarif + + validate-docker-compose: + name: Validate Docker Compose + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate docker-compose.yml + run: | + docker-compose -f docker-compose.yml config > /dev/null + continue-on-error: true diff --git a/atelier02-conteneurisation.pdf b/atelier02-conteneurisation.pdf new file mode 100755 index 0000000000000000000000000000000000000000..0acd1b09cc48e24d0c96a59bef3b258dddd07f45 GIT binary patch literal 50968 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 literal 0 HcmV?d00001 diff --git a/backend/Dockerfile b/backend/Dockerfile index f1004ef..329c0c4 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,9 +1,27 @@ -FROM python:3.12 - +# Stage 1: Tests et qualité +FROM python:3.12 as test WORKDIR /app -COPY . /app/ +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt flake8 bandit -RUN pip install -r requirements.txt +COPY . . +# Tests de qualité +RUN flake8 app/ --count --show-source --statistics || true +RUN bandit -r app/ -f json -o /tmp/bandit-report.json || true +# Stage 2: Application runtime +FROM python:3.12-slim as runtime +WORKDIR /app + +# Non-root user pour la sécurité +RUN useradd -m -u 1000 appuser + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY --from=test /app . +RUN chown -R appuser:appuser /app + +USER appuser EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..27114a7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3.9' + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + target: runtime + container_name: eni-backend + ports: + - "8000:8000" + environment: + - DATABASE_URL=sqlite:///./test.db + volumes: + - ./backend/app:/app/app + restart: unless-stopped + networks: + - eni-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/docs"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: eni-frontend + ports: + - "80:80" + environment: + - API_URL=http://backend:8000 + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + networks: + - eni-network + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + +networks: + eni-network: + driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..07f273a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,42 @@ +# Stage 1: Build +FROM node:20-alpine as builder +WORKDIR /app +COPY package*.json ./ +RUN npm install || true + +COPY . . +# Aucun build nécessaire pour cette app vanilla JS +# mais on peut valider la qualité du code +RUN npm run lint 2>/dev/null || echo "No lint script" + +# Stage 2: Runtime avec nginx +FROM nginx:alpine-slim +WORKDIR /usr/share/nginx/html + +# Copier les fichiers depuis le stage builder +COPY --from=builder /app . + +# Copier une configuration nginx pour le SPA +RUN cat > /etc/nginx/conf.d/default.conf << 'EOF' +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Cache les assets statiques + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Fallback pour le SPA + location / { + try_files $uri /index.html; + } +} +EOF + +USER nginx:nginx +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"]