From 5b0e7f399b4118483870917eb7f82ee3b117f06e Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 07:36:50 +0200 Subject: [PATCH 01/19] [fix] update Readme file --- .github/SECRETS_AND_VARIABLES.md | 609 ------------------------------- README.md | 4 - 2 files changed, 613 deletions(-) delete mode 100644 .github/SECRETS_AND_VARIABLES.md diff --git a/.github/SECRETS_AND_VARIABLES.md b/.github/SECRETS_AND_VARIABLES.md deleted file mode 100644 index 193ffd9..0000000 --- a/.github/SECRETS_AND_VARIABLES.md +++ /dev/null @@ -1,609 +0,0 @@ -# GitHub Actions - Secrets and Variables Setup Guide - -This document lists all secrets and variables required for the GitHub Actions workflows to function properly. - ---- - -## πŸ“‹ Table of Contents - -- [Quick Setup Checklist](#quick-setup-checklist) -- [Required Secrets](#required-secrets) -- [Optional Secrets](#optional-secrets) -- [Repository Variables](#repository-variables) -- [Repository Settings](#repository-settings) -- [Step-by-Step Setup Instructions](#step-by-step-setup-instructions) - ---- - -## βœ… Quick Setup Checklist - -### Minimal Setup (Required) - -- [ ] Enable GitHub Actions -- [ ] Configure repository permissions -- [ ] No secrets required! - -### Optional Enhancements - -- [ ] Container Registry access (for CD workflow) - ---- - -## πŸ”‘ Required Secrets - -### For Continuous Deployment (CD Workflow) - -If you want to deploy images to Docker Hub, you need these secrets: - -| Secret | Required | Purpose | -| ------------------------ | -------- | ------------------------------------------------------------------ | -| `DOCKER_USERNAME` | βœ… Yes | Your Docker Hub username | -| `DOCKERHUB_TOKEN` | βœ… Yes | Docker Hub Personal Access Token | -| `DOCKERHUB_PROJECT_NAME` | βœ… Yes | Name of your Docker Hub repository (e.g., `docker-reactjs-sample`) | - -**How to set up:** - -1. **Get Docker Hub Token:** - - ```bash - 1. Go to https://hub.docker.com - 2. Sign in to your account - 3. Click your username (top right) β†’ Account Settings - 4. Security β†’ New Access Token - 5. Description: "GitHub Actions" - 6. Permissions: "Read & Write" - 7. Generate and copy the token - ``` - -2. **Add secrets to GitHub:** - - ```bash - 1. Go to your repository on GitHub - 2. Settings β†’ Secrets and variables β†’ Actions - 3. Click "New repository secret" - - Secret 1: - - Name: DOCKER_USERNAME - - Value: your-dockerhub-username - - Secret 2: - - Name: DOCKERHUB_TOKEN - - Value: - - Secret 3: - - Name: DOCKERHUB_PROJECT_NAME - - Value: docker-reactjs-sample (or your project name) - ``` - -**Impact if not set:** - -- CI workflows (lint, test, build) will work fine -- Security workflow will work with limitations (Docker Scout may fail) -- Only CD workflow (deployment) will fail -- You can skip these if you don't need to push images to Docker Hub - -**Docker Scout Note:** - -- Docker Scout requires authentication to work properly -- The same `DOCKER_USERNAME` and `DOCKERHUB_TOKEN` are used for Scout -- Without these secrets, Scout will show "not entitled" error -- The workflow continues with graceful fallback (doesn't block CI) - ---- - -## πŸ” Automatic Secrets - -These secrets are automatically provided by GitHub: - -| Secret | Provided By | Purpose | -| -------------- | ------------ | ------------------------------------------------ | -| `GITHUB_TOKEN` | βœ… Automatic | GitHub API access, PR comments, artifact uploads | - -No configuration needed! Works out of the box. - ---- - -## πŸ” Optional Secrets - -Add these secrets only if you want to enable specific features: - -### Security Scanning (Snyk) - -**Used in:** `security.yml` -**Purpose:** Scan npm dependencies for vulnerabilities -**Required:** Optional, but highly recommended - -| Secret | Required | Purpose | -| ------------ | ----------- | ------------------------- | -| `SNYK_TOKEN` | ⚠️ Optional | Snyk authentication token | - -**How to get Snyk token:** - -1. Go to https://snyk.io -2. Sign up or log in (free tier available) -3. Go to Account Settings β†’ General -4. Copy your API token -5. Add to GitHub: Settings β†’ Secrets β†’ `SNYK_TOKEN` - -**Impact if not set:** - -- Security workflow will skip Snyk scan -- Docker Scout will still run -- You'll miss npm dependency vulnerability scanning - ---- - -### AI Code Review (OpenAI) - -**Used in:** `ai-code-review.yml` -**Purpose:** AI-powered code review on pull requests -**Required:** Optional - -| Secret | Required | Purpose | -| ---------------- | ----------- | ------------------------- | -| `OPENAI_API_KEY` | ⚠️ Optional | OpenAI API authentication | - -**How to get OpenAI API key:** - -1. Go to https://platform.openai.com -2. Sign up or log in -3. Go to API Keys β†’ Create new secret key -4. Copy the key (you won't see it again!) -5. Add to GitHub: Settings β†’ Secrets β†’ `OPENAI_API_KEY` - -**Cost considerations:** - -- Uses `gpt-4o-mini` by default (cost-effective) -- Typical cost: $0.01-0.05 per PR review -- Can upgrade to `gpt-4o` for better quality - -**Impact if not set:** - -- AI code review workflow will fail -- All other workflows will continue working -- You can delete the workflow if not needed - ---- - -### Container Registry Secrets (Optional) - -**Used in:** `cd.yml` (Continuous Deployment) -**Purpose:** Push multi-arch images to a container registry -**Required:** Only for deployment to production - -#### Using GitHub Container Registry (GHCR) - Recommended ⭐ - -**Why GHCR over Docker Hub?** - -βœ… **No Additional Secrets Required** - -- Uses `GITHUB_TOKEN` automatically -- No need to create or manage tokens -- Zero configuration needed - -βœ… **Tightly Integrated with GitHub** - -- Images appear in your repository's Packages tab -- Automatic linking between code and images -- Same permissions as your repository - -βœ… **Better for CI/CD** - -- No rate limits for private repositories -- Faster pulls from GitHub Actions -- Better caching and performance - -βœ… **No Rate Limits** - -- Docker Hub free tier: 200 pulls/6 hours -- GHCR: No pull limits for public repos -- Perfect for CI/CD pipelines - -βœ… **Cost-Effective** - -- Free for public repositories -- Included with GitHub Pro/Team -- No separate billing - -βœ… **Better for Teaching** - -- Simpler setup for your book readers -- Everything stays within GitHub -- One less external service to explain - -**No additional secrets needed!** Uses `GITHUB_TOKEN` automatically. - -**What you need to do:** - -1. Enable GHCR in your repository settings -2. Ensure workflow has `packages: write` permission (already configured) - -**Images will be pushed to:** - -``` -ghcr.io/your-username/docker-reactjs-sample:latest -ghcr.io/your-username/docker-reactjs-sample:main-abc1234 -ghcr.io/your-username/docker-reactjs-sample:v1.0.0 -``` - -**View your images:** - -``` -https://github.com/USERNAME/REPO/pkgs/container/docker-reactjs-sample -``` - ---- - -#### Using Docker Hub (Alternative) - -**Why you might choose Docker Hub:** - -⚠️ **More Complex Setup** - -- Requires separate account -- Need to create and manage access tokens -- Two secrets to configure - -⚠️ **Rate Limits** - -- Free tier: 200 pulls per 6 hours -- Can affect CI/CD pipelines -- Need Docker Hub Pro for higher limits - -⚠️ **Additional Maintenance** - -- Tokens can expire -- Separate billing if you exceed limits -- Need to manage two platforms - -**Use Docker Hub if:** - -- You already have a Docker Hub account -- You want images on Docker Hub specifically -- You need Docker Hub's public visibility - -If you prefer Docker Hub instead of GHCR: - -**Secrets needed:** - -- `DOCKER_USERNAME` - Your Docker Hub username -- `DOCKER_PASSWORD` - Docker Hub access token (not password!) - -**How to get Docker Hub token:** - -1. Go to https://hub.docker.com -2. Account Settings β†’ Security β†’ New Access Token -3. Generate token with "Read & Write" permissions - -**Update cd.yml:** - -```yaml -env: - REGISTRY: docker.io # Change from ghcr.io - IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/docker-reactjs-sample - -# Update login step: -- name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} -``` - ---- - -## πŸ“Š Repository Variables - -**Used in:** None currently -**Optional:** You can add variables for configuration - -### How to add variables: - -```bash -Settings β†’ Secrets and variables β†’ Actions β†’ Variables tab β†’ New repository variable -``` - -### Example variables you might add: - -```yaml -NODE_VERSION: "20" -DOCKER_BUILDKIT: "1" -NPM_CONFIG_LOGLEVEL: "error" -``` - -Then use in workflows: - -```yaml -- name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ vars.NODE_VERSION }} -``` - ---- - -## βš™οΈ Repository Settings - -### Required Permissions - -Go to: `Settings β†’ Actions β†’ General β†’ Workflow permissions` - -**Select:** βœ… Read and write permissions - -This allows workflows to: - -- Comment on pull requests -- Upload artifacts -- Create releases -- Push to GHCR (if using CD workflow) - -**Also enable:** βœ… Allow GitHub Actions to create and approve pull requests - ---- - -### Security Features - -Enable these for full security scanning capabilities: - -#### 1. Code Scanning - -Go to: `Settings β†’ Code security and analysis` - -**Enable:** - -- βœ… Dependency graph -- βœ… Dependabot alerts -- βœ… Dependabot security updates -- βœ… Code scanning (for SARIF uploads) - -#### 2. Secret Scanning - -**Enable:** - -- βœ… Secret scanning -- βœ… Push protection - ---- - -## πŸš€ Step-by-Step Setup Instructions - -### Initial Setup (5 minutes) - -#### Step 1: Enable GitHub Actions - -```bash -1. Go to your repository on GitHub -2. Click "Actions" tab -3. If disabled, click "I understand my workflows, go ahead and enable them" -``` - -#### Step 2: Configure Workflow Permissions - -```bash -1. Settings β†’ Actions β†’ General -2. Scroll to "Workflow permissions" -3. Select "Read and write permissions" -4. Check "Allow GitHub Actions to create and approve pull requests" -5. Click "Save" -``` - -#### Step 3: Enable Security Features - -```bash -1. Settings β†’ Code security and analysis -2. Enable all recommended features: - - Dependency graph βœ… - - Dependabot alerts βœ… - - Dependabot security updates βœ… - - Code scanning βœ… - - Secret scanning βœ… -``` - -#### Step 4: Configure Dependabot (Already done!) - -```bash -# Already configured in .github/dependabot.yml -# Dependabot will automatically: -# - Update npm dependencies weekly -# - Update GitHub Actions weekly -# - Update Docker base images weekly -``` - -βœ… **Done!** All workflows will now work. - ---- - -### Optional: Enable Container Deployment (10 minutes) - -Only do this if you want to deploy images to GHCR: - -#### Using GHCR (Recommended - No secrets needed!) - -```bash -1. Go to repository Settings β†’ Actions β†’ General -2. Under "Workflow permissions", ensure "packages: write" is enabled -3. Push to main branch or create a tag -4. Images will be pushed to ghcr.io automatically -5. View images at: github.com/USERNAME/REPO/pkgs/container/docker-reactjs-sample -``` - -#### Using Docker Hub (Alternative) - -```bash -1. Get Docker Hub token: - - Go to https://hub.docker.com - - Settings β†’ Security β†’ New Access Token - - Name: "GitHub Actions" - - Permissions: "Read & Write" - - Copy the token - -2. Add secrets to GitHub: - Settings β†’ Secrets and variables β†’ Actions - - Secret 1: - - Name: DOCKER_USERNAME - - Value: your-dockerhub-username - - Secret 2: - - Name: DOCKER_PASSWORD - - Value: - -3. Update cd.yml (see example in Optional Secrets section) -``` - ---- - -## πŸ” Verifying Your Setup - -### Test Basic CI (No secrets required) - -```bash -# Create a test branch -git checkout -b test-actions -git commit --allow-empty -m "test: verify GitHub Actions" -git push origin test-actions - -# Create a Pull Request on GitHub -# Check Actions tab - should see: -βœ… Lint Code Quality -βœ… Test & Coverage -βœ… Build & Validate -βœ… Security Scan (Docker Scout + Snyk if token provided) -βœ… AI Code Review (if OPENAI_API_KEY provided) -``` - -### Test Container Deployment (If configured) - -```bash -# Push to main or create a tag -git tag v1.0.0 -git push origin v1.0.0 - -# Check cd.yml workflow run -# Should see: "βœ… Multi-arch images pushed" -# View images: -# GHCR: https://github.com/USERNAME/REPO/pkgs/container/docker-reactjs-sample -# Docker Hub: https://hub.docker.com/r/USERNAME/docker-reactjs-sample -``` - ---- - -## πŸ“Š Secrets Summary Table - -| Secret | Required | Used By | Purpose | Get From | -| ------------------------ | ----------- | ------------------ | --------------------------- | ------------------- | -| `GITHUB_TOKEN` | βœ… Auto | All workflows | GitHub API access | Automatic | -| `DOCKER_USERNAME` | βœ… For CD | cd.yml, security.yml | Docker Hub login | hub.docker.com | -| `DOCKERHUB_TOKEN` | βœ… For CD | cd.yml, security.yml | Docker Hub push access | hub.docker.com | -| `DOCKERHUB_PROJECT_NAME` | βœ… For CD | cd.yml | Docker Hub project name | hub.docker.com | -| `SNYK_TOKEN` | ⚠️ Optional | security.yml | Snyk vulnerability scanning | snyk.io | -| `OPENAI_API_KEY` | ⚠️ Optional | ai-code-review.yml | AI-powered code reviews | platform.openai.com | - -> **Note:** `DOCKER_USERNAME` and `DOCKERHUB_TOKEN` are also used by `security.yml` to authenticate Docker Scout scans. - ---- - -## 🎯 Workflow Capabilities by Setup Level - -### Level 1: Minimal (No secrets) - -βœ… Linting with inline PR comments (ESLint + Reviewdog) -βœ… Testing with 100% coverage reports -βœ… Docker build validation -βœ… Security scanning (Docker Scout) -βœ… Dependabot dependency updates -βœ… PR comments and summaries -βœ… Coverage reports in PRs (Vitest Coverage Report Action) - -### Level 2: With Security Scanning (+ SNYK_TOKEN) - -βœ… Everything from Level 1 -βœ… Snyk npm dependency scanning -βœ… Comprehensive vulnerability detection -βœ… SARIF reports in GitHub Security tab - -### Level 3: With AI Code Review (+ OPENAI_API_KEY) - -βœ… Everything from Level 1 & 2 -βœ… AI-powered code review on PRs -βœ… ChatGPT & CodeRabbit reviews -βœ… Best practices suggestions -βœ… Security & performance recommendations - -### Level 4: Full Production Setup (All secrets) - -βœ… Everything from all levels -βœ… Multi-architecture image builds -βœ… Automated deployments to Docker Hub -βœ… Image versioning and tagging -βœ… Production-ready releases - ---- - -## πŸ†˜ Troubleshooting - -### "Resource not accessible by integration" - -**Problem:** Workflow can't comment on PRs or upload artifacts - -**Solution:** - -```bash -Settings β†’ Actions β†’ General β†’ Workflow permissions -Select: "Read and write permissions" βœ… -``` - -### "Docker login failed" - -**Problem:** Container registry credentials invalid - -**Solution:** - -- For GHCR: Check `packages: write` permission -- For Docker Hub: Verify DOCKER_USERNAME and DOCKER_PASSWORD secrets -- Ensure using token, not password for Docker Hub - -### "SARIF upload failed" - -**Problem:** Code scanning not enabled - -**Solution:** - -```bash -Settings β†’ Code security and analysis -Enable: "Code scanning" βœ… -``` - ---- - -## πŸ“š Additional Resources - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [GitHub Secrets Documentation](https://docs.github.com/en/actions/security-guides/encrypted-secrets) -- [Dependabot Documentation](https://docs.github.com/en/code-security/dependabot) -- [Docker Scout Documentation](https://docs.docker.com/scout/) -- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) -- [Vitest Coverage Report Action](https://github.com/marketplace/actions/vitest-coverage-report) -- [Reviewdog ESLint Action](https://github.com/marketplace/actions/eslint-reviewdog) - ---- - -## βœ… Final Checklist - -Before pushing your first commit: - -- [ ] GitHub Actions enabled -- [ ] Workflow permissions set to "Read and write" -- [ ] Security features enabled (Dependabot, Code scanning) -- [ ] Dependabot configuration reviewed (`.github/dependabot.yml`) -- [ ] Container registry configured (if deploying) -- [ ] Test workflows triggered successfully -- [ ] PR comments working -- [ ] Security scans completing - ---- - -**Perfect for your book: "Docker for React.js Developers"** πŸŽ‰ - -The setup is intentionally minimal - workflows work out of the box with no secrets required for the core functionality! diff --git a/README.md b/README.md index b84023e..57f6336 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9.2-blue.svg)](https://www.typescriptlang.org/) [![Vite](https://img.shields.io/badge/Vite-7.1.5-646CFF.svg)](https://vitejs.dev/) -[![CI](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/ci.yml/badge.svg)](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/ci.yml) -[![Security](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/security.yml/badge.svg)](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/security.yml) -[![CD](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/cd.yml/badge.svg)](https://github.com/kristiyan-velkov/docker-reactjs-sample/actions/workflows/cd.yml) - A comprehensive demonstration of containerizing a modern React.js application using Docker for both **development** and **production** workflows. This project showcases industry best practices for front-end containerization, including secure builds, streamlined development workflows, and optimized production delivery. Part of the [official Docker React.js sample](https://docs.docker.com/guides/reactjs/) guide. From aa93b364f2e889e80c2a528b41c909c9a68e0df2 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 07:39:33 +0200 Subject: [PATCH 02/19] [fix] update Readme file --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57f6336..63588c5 100644 --- a/README.md +++ b/README.md @@ -162,10 +162,10 @@ Run the test suite: ```bash # Local testing -npm test +npm run test # Testing in Docker -docker compose exec app npm test +docker compose exec app npm run test ``` ## πŸ›‘οΈ Security From 7e59745af7e4ba2c7712043031f56a9b13165010 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 10:25:07 +0200 Subject: [PATCH 03/19] [fix] added dockerhub_token as a env variable --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e529d0..095c204 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,7 @@ concurrency: env: NODE_VERSION: "22.11.0" DOCKERHUB_REPO: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKERHUB_PROJECT_NAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} jobs: # Step 1: Code Quality Checks From ec04d3004a0bdeb3c1bf8e3866edcb0e120e792a Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 10:25:45 +0200 Subject: [PATCH 04/19] [fix] remove docker hub token as a env --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 095c204..9e529d0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,6 @@ concurrency: env: NODE_VERSION: "22.11.0" DOCKERHUB_REPO: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKERHUB_PROJECT_NAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} jobs: # Step 1: Code Quality Checks From d7d54481179704fedeaad0c2e10d83e67ec4eeb4 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:13:43 +0200 Subject: [PATCH 05/19] [feat] update github main workflow to text deployment to aws ecr --- .dockerignore | 57 +++++++++++++++++++++ .github/workflows/main.yml | 102 ++++++++++++++++++++++++++++--------- 2 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..15def4b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,57 @@ +# Ignore dependencies and build output +node_modules/ +dist/ +out/ +.tmp/ +.cache/ + +# Ignore Vite, Webpack, and React-specific build artifacts +.vite/ +.vitepress/ +.eslintcache +.npm/ +coverage/ +jest/ +cypress/ +cypress/screenshots/ +cypress/videos/ +reports/ + +# Ignore environment and config files (sensitive data) +*.env* +*.log + +# Ignore TypeScript build artifacts (if using TypeScript) +*.tsbuildinfo + +# Ignore lockfiles (optional if using Docker for package installation) +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Ignore local development files +.git/ +.gitignore +.vscode/ +.idea/ +*.swp +.DS_Store +Thumbs.db + +# Ignore Docker-related files (to avoid copying unnecessary configs) +Dockerfile +.dockerignore +docker-compose.yml +docker-compose.override.yml + +# Ignore build-specific cache files +*.lock + +# Ignore AI generated files +.ai/ +.ai-temp/ +.cursor/ +.claude/ +.kiro/ +.vscode-ai/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e529d0..1c6813b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,20 +67,70 @@ jobs: retention-days: 7 # Step 3: Build and Deploy (only on main branch) + # build-and-deploy: + # name: Build & Deploy to Docker Hub + # runs-on: ubuntu-latest + # needs: [lint, test] + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v3 + + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v3 + + # - name: Extract metadata + # id: meta + # run: | + # echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + # echo "date=$(date +'%Y%m%d')" >> "$GITHUB_OUTPUT" + + # - name: Log in to Docker Hub + # uses: docker/login-action@v3 + # with: + # username: ${{ secrets.DOCKER_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + + # - name: Build and push production image + # uses: docker/build-push-action@v6 + # with: + # context: . + # file: Dockerfile + # push: true + # platforms: linux/amd64,linux/arm64 + # tags: | + # ${{ env.DOCKERHUB_REPO }}:latest + # ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.short_sha }} + # ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.date }} + # cache-from: type=gha + # cache-to: type=gha,mode=max + + # Step 4: Build and Deploy to AWS + # Step 2: Build and (conditionally) Deploy build-and-deploy: - name: Build & Deploy to Docker Hub + name: Build & Deploy to AWS ECS Fargate runs-on: ubuntu-latest needs: [lint, test] + # Only run on develop branch (For test purposes). To be replaced with main branch when ready. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Login to Amazon ECR + uses: aws-actions/amazon-ecr-login@v2 - name: Extract metadata id: meta @@ -88,22 +138,28 @@ jobs: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" echo "date=$(date +'%Y%m%d')" >> "$GITHUB_OUTPUT" - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build Docker image + id: build-image + run: | + IMAGE_TAG=${{ steps.meta.outputs.short_sha }} + ECR_IMAGE=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG + docker build -t $ECR_IMAGE . + echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV + + # Only push image & deploy when pushing to main + - name: Push image to ECR + # Only run on develop branch (For test purposes). To be replaced with main branch when ready. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + run: | + docker push $ECR_IMAGE - - name: Build and push production image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.DOCKERHUB_REPO }}:latest - ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.short_sha }} - ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.date }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Force new ECS deployment + # Only run on develop branch (For test purposes). To be replaced with main branch when ready. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + run: | + echo "Triggering ECS service update..." + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ env.ECS_SERVICE }} \ + --force-new-deployment \ + --region ${{ secrets.AWS_REGION }} From e09426479faa1be962d41107b3f8503e3a7a6115 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:23:23 +0200 Subject: [PATCH 06/19] [feat] update main github action --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c6813b..f433755 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -142,7 +142,7 @@ jobs: id: build-image run: | IMAGE_TAG=${{ steps.meta.outputs.short_sha }} - ECR_IMAGE=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG + ECR_IMAGE=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_REPOSITORY_NAME }}:$IMAGE_TAG docker build -t $ECR_IMAGE . echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV @@ -159,7 +159,6 @@ jobs: run: | echo "Triggering ECS service update..." aws ecs update-service \ - --cluster ${{ env.ECS_CLUSTER }} \ - --service ${{ env.ECS_SERVICE }} \ + --cluster ${{ secrets.ECS_CLUSTER_NAME }} \ --force-new-deployment \ --region ${{ secrets.AWS_REGION }} From ee682163596b9c49038da0e8e2bc8c3f7e1d85db Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:33:36 +0200 Subject: [PATCH 07/19] [feat] update the workflow --- .github/workflows/main.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f433755..9c6a67f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -142,7 +142,7 @@ jobs: id: build-image run: | IMAGE_TAG=${{ steps.meta.outputs.short_sha }} - ECR_IMAGE=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ secrets.ECR_REPOSITORY_NAME }}:$IMAGE_TAG + ECR_IMAGE=${{ secrets.ECR_REPOSITORY_URL }}:$IMAGE_TAG docker build -t $ECR_IMAGE . echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV @@ -153,12 +153,11 @@ jobs: run: | docker push $ECR_IMAGE - - name: Force new ECS deployment + - name: Deploy to Amazon ECS # Only run on develop branch (For test purposes). To be replaced with main branch when ready. if: github.event_name == 'push' && github.ref == 'refs/heads/develop' run: | - echo "Triggering ECS service update..." aws ecs update-service \ --cluster ${{ secrets.ECS_CLUSTER_NAME }} \ - --force-new-deployment \ - --region ${{ secrets.AWS_REGION }} + --service ${{ secrets.ECS_SERVICE_NAME }} \ + --force-new-deployment From 956dd85a2101091b25c88c2ff473925ff4206819 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:38:40 +0200 Subject: [PATCH 08/19] [feat] update the main git workflow --- .github/workflows/main.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c6a67f..684f372 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -109,7 +109,6 @@ jobs: # cache-to: type=gha,mode=max # Step 4: Build and Deploy to AWS - # Step 2: Build and (conditionally) Deploy build-and-deploy: name: Build & Deploy to AWS ECS Fargate runs-on: ubuntu-latest @@ -130,6 +129,7 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} - name: Login to Amazon ECR + id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Extract metadata @@ -146,16 +146,11 @@ jobs: docker build -t $ECR_IMAGE . echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV - # Only push image & deploy when pushing to main - name: Push image to ECR - # Only run on develop branch (For test purposes). To be replaced with main branch when ready. - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' run: | docker push $ECR_IMAGE - name: Deploy to Amazon ECS - # Only run on develop branch (For test purposes). To be replaced with main branch when ready. - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' run: | aws ecs update-service \ --cluster ${{ secrets.ECS_CLUSTER_NAME }} \ From f6181ff48ba285ce54e8d584883dacf5416583ba Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:42:36 +0200 Subject: [PATCH 09/19] [fix] git workflow --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 684f372..53401a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -140,9 +140,11 @@ jobs: - name: Build Docker image id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} run: | IMAGE_TAG=${{ steps.meta.outputs.short_sha }} - ECR_IMAGE=${{ secrets.ECR_REPOSITORY_URL }}:$IMAGE_TAG + ECR_IMAGE=$ECR_REGISTRY/${{ secrets.ECR_REPOSITORY_NAME }}:$IMAGE_TAG docker build -t $ECR_IMAGE . echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV From d1a9c65f7ac52c6f8c6840950ffa129b10d66736 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:45:47 +0200 Subject: [PATCH 10/19] [feat] addded create repo in aws step --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53401a5..433d399 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,6 +132,11 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v2 + - name: Create ECR repository if it doesn't exist + run: | + aws ecr describe-repositories --repository-names ${{ secrets.ECR_REPOSITORY_NAME }} --region ${{ secrets.AWS_REGION }} || \ + aws ecr create-repository --repository-name ${{ secrets.ECR_REPOSITORY_NAME }} --region ${{ secrets.AWS_REGION }} + - name: Extract metadata id: meta run: | From bdf3bfd77ab5232c22190b6bfa34459bc46d0852 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Mon, 10 Nov 2025 20:54:29 +0200 Subject: [PATCH 11/19] [fix] main.yml git workflow --- .github/workflows/main.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 433d399..e303e64 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,11 +132,6 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - - name: Create ECR repository if it doesn't exist - run: | - aws ecr describe-repositories --repository-names ${{ secrets.ECR_REPOSITORY_NAME }} --region ${{ secrets.AWS_REGION }} || \ - aws ecr create-repository --repository-name ${{ secrets.ECR_REPOSITORY_NAME }} --region ${{ secrets.AWS_REGION }} - - name: Extract metadata id: meta run: | @@ -160,6 +155,7 @@ jobs: - name: Deploy to Amazon ECS run: | aws ecs update-service \ - --cluster ${{ secrets.ECS_CLUSTER_NAME }} \ - --service ${{ secrets.ECS_SERVICE_NAME }} \ - --force-new-deployment + --cluster "${{ secrets.ECS_CLUSTER_NAME }}" \ + --service "${{ secrets.ECS_SERVICE_NAME }}" \ + --force-new-deployment \ + --region ${{ secrets.AWS_REGION }} From 2007f8cca8b359c973692630e3c104f8248703aa Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Tue, 11 Nov 2025 13:34:59 +0200 Subject: [PATCH 12/19] [feat] check the deployment --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e303e64..43719d8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -159,3 +159,5 @@ jobs: --service "${{ secrets.ECS_SERVICE_NAME }}" \ --force-new-deployment \ --region ${{ secrets.AWS_REGION }} + + From 4ec399b671e3fe9f774836c305102ed5e5927a48 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Wed, 12 Nov 2025 11:51:29 +0200 Subject: [PATCH 13/19] [feat] test the deployment to the AWS ECR --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43719d8..e303e64 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -159,5 +159,3 @@ jobs: --service "${{ secrets.ECS_SERVICE_NAME }}" \ --force-new-deployment \ --region ${{ secrets.AWS_REGION }} - - From cff6a65955569932ee449a87028a19d3098d4700 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Wed, 12 Nov 2025 11:57:39 +0200 Subject: [PATCH 14/19] [feat] update the main.yml workflow to have also latest tag --- .github/workflows/main.yml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e303e64..37d8cc7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -136,21 +136,33 @@ jobs: id: meta run: | echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - echo "date=$(date +'%Y%m%d')" >> "$GITHUB_OUTPUT" - name: Build Docker image id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} run: | - IMAGE_TAG=${{ steps.meta.outputs.short_sha }} - ECR_IMAGE=$ECR_REGISTRY/${{ secrets.ECR_REPOSITORY_NAME }}:$IMAGE_TAG - docker build -t $ECR_IMAGE . - echo "ECR_IMAGE=$ECR_IMAGE" >> $GITHUB_ENV + # Create multiple tags following best practices + IMAGE_TAG_SHA=${{ steps.meta.outputs.short_sha }} + + ECR_IMAGE_SHA=$ECR_REGISTRY/${{ secrets.ECR_REPOSITORY_NAME }}:$IMAGE_TAG_SHA + ECR_IMAGE_LATEST=$ECR_REGISTRY/${{ secrets.ECR_REPOSITORY_NAME }}:latest + + # Build with multiple tags + docker build \ + -t $ECR_IMAGE_SHA \ + -t $ECR_IMAGE_LATEST . + + # Export for use in deployment (use SHA for precise tracking) + echo "ECR_IMAGE=$ECR_IMAGE_SHA" >> $GITHUB_ENV - name: Push image to ECR run: | - docker push $ECR_IMAGE + # Push all tags to ECR + docker push $ECR_IMAGE_SHA + docker push $ECR_IMAGE_LATEST + + echo "βœ… Pushed image with tags: $ECR_IMAGE_SHA and $ECR_IMAGE_LATEST to ECR" - name: Deploy to Amazon ECS run: | @@ -159,3 +171,5 @@ jobs: --service "${{ secrets.ECS_SERVICE_NAME }}" \ --force-new-deployment \ --region ${{ secrets.AWS_REGION }} + + echo "βœ… Deployed service ${{ secrets.ECS_SERVICE_NAME }} to ECS cluster ${{ secrets.ECS_CLUSTER_NAME }}" From 5d57c7bb0b9b8b7e2300a9c473a76da94785ed5a Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Wed, 12 Nov 2025 12:02:27 +0200 Subject: [PATCH 15/19] [fix] main.yml workflow --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37d8cc7..51c3ca1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -153,8 +153,10 @@ jobs: -t $ECR_IMAGE_SHA \ -t $ECR_IMAGE_LATEST . - # Export for use in deployment (use SHA for precise tracking) + # Export for use in next steps echo "ECR_IMAGE=$ECR_IMAGE_SHA" >> $GITHUB_ENV + echo "ECR_IMAGE_SHA=$ECR_IMAGE_SHA" >> $GITHUB_ENV + echo "ECR_IMAGE_LATEST=$ECR_IMAGE_LATEST" >> $GITHUB_ENV - name: Push image to ECR run: | From 900da40afa4aa1778546cafc4205c3f0d83f4169 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Fri, 21 Nov 2025 18:37:45 +0200 Subject: [PATCH 16/19] [feat] update dockerignore, docker compose, Dockerfile and Docker.serve node.js version. --- .dockerignore | 86 +++++++++++++++++++++++++---------------------- Dockerfile | 2 +- Dockerfile.serve | 65 ++++++++++++----------------------- compose.yaml | 11 +++++- package-lock.json | 12 +++---- 5 files changed, 85 insertions(+), 91 deletions(-) diff --git a/.dockerignore b/.dockerignore index 15def4b..1263f4b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,54 +1,60 @@ -# Ignore dependencies and build output -node_modules/ -dist/ -out/ -.tmp/ -.cache/ - -# Ignore Vite, Webpack, and React-specific build artifacts -.vite/ -.vitepress/ +# Dependencies +node_modules +.pnpm-store +.npm +.yarn + +# Build outputs +dist +out +.tmp +.cache + +# Vite and React artifacts +.vite .eslintcache -.npm/ -coverage/ -jest/ -cypress/ -cypress/screenshots/ -cypress/videos/ -reports/ - -# Ignore environment and config files (sensitive data) -*.env* -*.log - -# Ignore TypeScript build artifacts (if using TypeScript) *.tsbuildinfo -# Ignore lockfiles (optional if using Docker for package installation) -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* +# Environment and secrets +.env.* +*.pem +*.key -# Ignore local development files -.git/ -.gitignore -.vscode/ -.idea/ +# Logs +*.log + +# IDE and Editor files +.idea +.vscode +.vs *.swp +*.swo .DS_Store Thumbs.db +.git +.gitignore +.editorconfig -# Ignore Docker-related files (to avoid copying unnecessary configs) -Dockerfile +# Docker +Dockerfile* +compose.yaml .dockerignore -docker-compose.yml -docker-compose.override.yml -# Ignore build-specific cache files -*.lock +# CI/CD and Tools +.github +Taskfile.yml + +# Documentation and Assets +*.md +images/ + +# Test files +**/*.test.ts +**/*.test.tsx +src/setupTests.ts + -# Ignore AI generated files +# AI generated files .ai/ .ai-temp/ .cursor/ diff --git a/Dockerfile b/Dockerfile index 4bee334..4d74453 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ========================================= # Stage 1: Build the React.js Application # ========================================= -ARG NODE_VERSION=24.11.0-alpine +ARG NODE_VERSION=24.11.1-alpine ARG NGINX_VERSION=alpine3.22 # Use a lightweight Node.js image for building (customizable via ARG) diff --git a/Dockerfile.serve b/Dockerfile.serve index 16cbeb8..04b394d 100644 --- a/Dockerfile.serve +++ b/Dockerfile.serve @@ -1,70 +1,49 @@ -# This Dockerfile is used to serve the React.js application using the Vercel serve npm package. +# ============================= +# Stage 1: Build the application +# ============================= +ARG NODE_VERSION=24.11.1 -# ========================================= -# Stage 1: Serve the React.js Application -# ========================================= +# Use a lightweight Node.js Alpine image for building +FROM node:${NODE_VERSION}-alpine AS build -# Define Node.js version -ARG NODE_VERSION=24.11.0 - -# ----------------------------------------------------------------------------- -# 1. BASE STAGE β€” Common setup -# ----------------------------------------------------------------------------- -FROM node:${NODE_VERSION}-alpine AS base - -# Set working directory +# Set the working directory inside the container WORKDIR /app -# Copy dependency manifests -COPY package*.json ./ +# Copy dependency manifests first to optimize Docker caching +COPY package.json package-lock.json ./ -# ----------------------------------------------------------------------------- -# Stage 2: DEPENDENCIES STAGE β€” Install only production dependencies -# ----------------------------------------------------------------------------- -FROM base AS deps - -# Use BuildKit cache to speed up npm installs -RUN --mount=type=cache,target=/root/.npm \ - npm ci --omit=dev - -# ----------------------------------------------------------------------------- -# Stage 3: BUILD STAGE β€” Install all dependencies and build the app -# ----------------------------------------------------------------------------- -FROM base AS build - -# Install all dependencies (including devDependencies) +# Install all dependencies (including devDependencies) using BuildKit cache RUN --mount=type=cache,target=/root/.npm \ npm ci -# Copy the rest of the project files +# Copy the rest of the project files into the container COPY . . -# Build the production bundle +# Build the React.js production bundle RUN npm run build -# ----------------------------------------------------------------------------- -# Stage 4: FINAL STAGE β€” Minimal runtime image -# ----------------------------------------------------------------------------- +# ============================= +# Stage 2: Final Runtime Image +# ============================= FROM node:${NODE_VERSION}-alpine AS final -# Set environment variables for production +# Set production environment ENV NODE_ENV=production -# Set working directory +# Set working directory for the runtime container WORKDIR /app -# Installing globally requires root access, so do this before USER node +# Install the Vercel "serve" package globally to serve static files RUN npm install -g serve - -# Switch to a non-root user for security +# Drop root privileges and run as the non-root "node" user USER node -# Copy only the built app from the build stage +# Copy the built production files from the build stage COPY --from=build /app/dist /app/build -# Expose port 8080 for the static server +# Expose the port where the static server will run EXPOSE 8080 -# Start the static file server +# Start the static server to serve the built React app CMD ["serve", "-s", "build", "-l", "8080"] diff --git a/compose.yaml b/compose.yaml index 401b66b..b308a59 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,12 +4,16 @@ services: context: . dockerfile: Dockerfile image: docker-reactjs-sample + container_name: reactjs-sample-app-prod ports: - "8080:8080" + react-dev: build: context: . dockerfile: Dockerfile.dev + image: docker-reactjs-sample-dev + container_name: reactjs-sample-app-prod ports: - "5173:5173" develop: @@ -17,14 +21,19 @@ services: - action: sync path: . target: /app + react-test: build: context: . dockerfile: Dockerfile.dev - command: ["npm", "run", "test:coverage"] + image: docker-reactjs-sample-test + container_name: reactjs-sample-app-test + command: ["npm", "run", "test"] react-lint: build: context: . dockerfile: Dockerfile.dev + image: docker-reactjs-sample-lint + container_name: reactjs-sample-app-lint command: ["npm", "run", "lint"] diff --git a/package-lock.json b/package-lock.json index 16f49d9..7bcc3f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5229,9 +5229,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -6321,9 +6321,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { From 360885b675374a688a664721fcd35f504d94c820 Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Sat, 22 Nov 2025 08:10:52 +0200 Subject: [PATCH 17/19] [feat] update the nginx config --- nginx.conf | 83 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/nginx.conf b/nginx.conf index e16e0e9..bd7759a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,57 +1,66 @@ -worker_processes auto; +worker_processes auto; # Use all available CPU cores for maximum efficiency -# Store PID in /tmp (always writable) -pid /tmp/nginx.pid; +# Store PID in /tmp (this directory is always writable in containers) +pid /tmp/nginx.pid; # Prevent permission issues inside Docker containers events { - worker_connections 1024; + worker_connections 1024; # Max concurrent connections per worker } http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Disable logging to avoid permission issues - access_log off; - error_log /dev/stderr warn; - - # Optimize static file serving - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - keepalive_requests 1000; - - # Gzip compression for optimized delivery - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; - gzip_min_length 256; - gzip_vary on; + include /etc/nginx/mime.types; # Load standard MIME types for correct content handling + default_type application/octet-stream; # Fallback MIME type for unknown file types + + # Disable access logs to avoid permission issues and reduce output noise + access_log off; # Turn off access logs completely + error_log /dev/stderr warn; # Send error logs to stderr for visibility in Docker + + # Optimize static file delivery + sendfile on; # Enable efficient file transfers using kernel mechanisms + tcp_nopush on; # Optimize packet sizes when sending large files + tcp_nodelay on; # Disable buffering for low-latency responses + keepalive_timeout 65; # Time to keep connections open for reuse + keepalive_requests 1000; # Allow many requests per connection before closing + + # Enable gzip compression for faster load times + gzip on; # Turn on gzip compression + gzip_types # Specify which file types should be compressed + text/plain + text/css + application/json + application/javascript + text/xml + application/xml + application/xml+rss + text/javascript + image/svg+xml; + gzip_min_length 256; # Only compress responses larger than 256 bytes + gzip_vary on; # Add Vary: Accept-Encoding header for proxies/CDNs server { - listen 8080; - server_name localhost; + listen 8080; # Expose the app on port 8080 + server_name localhost; # Default server name for local setups - # Root directory where React.js build files are placed - root /usr/share/nginx/html; - index index.html; + # Root directory where the React build artifacts (index.html, static folder, etc.) are placed + root /usr/share/nginx/html; # Serve files from this directory + index index.html; # Default file served on directory access - # Serve React.js static files with proper caching + # Main route handler β€” supports client-side routing in React location / { - try_files $uri /index.html; + try_files $uri /index.html; # If file is missing, fall back to index.html for SPA routing } - # Serve static assets with long cache expiration + # Cache static assets aggressively for better performance location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|map)$ { - expires 1y; - access_log off; - add_header Cache-Control "public, immutable"; + expires 1y; # Cache for 1 year (safe for hashed filenames) + access_log off; # Disable logs for static files + add_header Cache-Control "public, immutable"; # Tell browsers that files won't change } - # Handle React.js client-side routing + # Additional static assets path (React’s /static/ directory) location /static/ { - expires 1y; - add_header Cache-Control "public, immutable"; + expires 1y; # Cache assets from /static for 1 year + add_header Cache-Control "public, immutable"; # Ensure long-term caching } } } From 5a00d3ad12b08cded9239e9acbda9eb5c401dd8e Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Tue, 25 Nov 2025 22:36:58 +0200 Subject: [PATCH 18/19] [feat] added improvements and ai code rules for best Docker files generation --- .ai/AGENTS.md | 615 +++++++++++++++++++++++++++++++++++++++++++++++ .ai/rules | 136 +++++++++++ Dockerfile | 6 +- Dockerfile.dev | 16 +- Dockerfile.serve | 10 +- compose.yaml | 2 +- 6 files changed, 773 insertions(+), 12 deletions(-) create mode 100644 .ai/AGENTS.md create mode 100644 .ai/rules diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md new file mode 100644 index 0000000..ccc4450 --- /dev/null +++ b/.ai/AGENTS.md @@ -0,0 +1,615 @@ +# Docker Best Practices Guide + +This document outlines Docker best practices and standards for creating production-ready Dockerfiles and Docker Compose configurations. + +## Table of Contents + +1. [Image Version Pinning](#image-version-pinning) +2. [Base Image Selection](#base-image-selection) +3. [Multi-Stage Builds](#multi-stage-builds) +4. [Non-Root User](#non-root-user) +5. [Layer Caching Optimization](#layer-caching-optimization) +6. [Security Best Practices](#security-best-practices) +7. [Docker Compose Best Practices](#docker-compose-best-practices) +8. [.dockerignore Best Practices](#dockerignore-best-practices) +9. [Common Mistakes to Avoid](#common-mistakes-to-avoid) + +--- + +## Image Version Pinning + +### βœ… DO: Pin Exact Versions + +Always pin exact versions of base images and dependencies to ensure reproducible builds. + +```dockerfile +# βœ… GOOD - Pinned exact version +ARG NODE_VERSION=24.11.1-alpine +ARG NGINX_VERSION=1.27.3-alpine3.22 +FROM node:${NODE_VERSION} AS builder +FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner + +# ❌ BAD - Using latest or floating tags +FROM node:latest +FROM node:24-alpine +FROM nginx:alpine +``` + +**Why?** + +- Prevents unexpected breaking changes +- Ensures consistent builds across environments +- Makes security updates intentional and traceable +- Improves build reproducibility + +--- + +## Base Image Selection + +### βœ… DO: Use Alpine or Slim Variants + +Prefer Alpine Linux or Debian Slim variants for smaller image sizes and reduced attack surface. + +```dockerfile +# βœ… GOOD - Alpine variant (smallest, ~5MB base) +FROM node:24.11.1-alpine + +# βœ… GOOD - Slim variant (Debian-based, ~70MB base) +FROM node:24.11.1-slim + +# ❌ BAD - Full image (Debian-based, ~300MB+ base) +FROM node:24.11.1 +``` + +**Why?** + +- Smaller image size = faster pulls and deployments +- Fewer packages = reduced attack surface +- Lower resource consumption +- Alpine uses musl libc (may have compatibility considerations) + +**When to use Alpine:** + +- Most Node.js applications +- Static file serving +- Microservices + +**When to use Slim:** + +- Applications requiring glibc compatibility +- Applications with native dependencies that don't work with musl + +--- + +## Multi-Stage Builds + +### βœ… DO: Use Multi-Stage Builds + +Separate build dependencies from runtime dependencies to minimize final image size. + +```dockerfile +# βœ… GOOD - Multi-stage build +# Stage 1: Build +FROM node:24.11.1-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# Stage 2: Runtime +FROM node:24.11.1-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/package.json ./ +RUN npm ci --only=production +USER node +CMD ["node", "server.js"] + +# ❌ BAD - Single stage with all dependencies +FROM node:24.11.1-alpine +WORKDIR /app +COPY . . +RUN npm install # Includes devDependencies +RUN npm run build +CMD ["node", "server.js"] +``` + +**Benefits:** + +- Final image only contains runtime dependencies +- Significantly smaller image size +- Better security (no build tools in production) +- Faster deployments + +**Best Practices:** + +- Name stages descriptively (`builder`, `runner`, `test`) +- Copy only necessary artifacts between stages +- Use `--from=` to reference previous stages + +--- + +## Non-Root User + +### βœ… DO: Run as Non-Root User + +Always run containers as a non-root user to minimize security risks. + +```dockerfile +# βœ… GOOD - Using built-in non-root user +FROM node:24.11.1-alpine +WORKDIR /app +COPY --chown=node:node . . +USER node +CMD ["node", "server.js"] + +# βœ… GOOD - Creating custom non-root user +FROM node:24.11.1-alpine +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 +WORKDIR /app +COPY --chown=nodejs:nodejs . . +USER nodejs +CMD ["node", "server.js"] + +# βœ… GOOD - Using official non-root images +FROM nginxinc/nginx-unprivileged:1.27.3-alpine3.22 +COPY --chown=nginx:nginx . /usr/share/nginx/html +USER nginx + +# ❌ BAD - Running as root +FROM node:24.11.1-alpine +WORKDIR /app +COPY . . +# No USER directive = runs as root +CMD ["node", "server.js"] +``` + +**Why?** + +- Principle of least privilege +- Reduces impact of container escape vulnerabilities +- Required by many security scanners and policies +- Best practice for production deployments + +**Implementation Tips:** + +- Use `--chown` flag when copying files +- Set ownership before switching users +- Use fixed UIDs/GIDs for consistency +- Prefer official non-root images when available + +--- + +## Layer Caching Optimization + +### βœ… DO: Optimize Layer Ordering + +Order Dockerfile instructions from least to most frequently changing. + +```dockerfile +# βœ… GOOD - Optimal layer ordering +FROM node:24.11.1-alpine AS builder +WORKDIR /app + +# 1. Copy dependency files first (changes infrequently) +COPY package.json package-lock.json ./ + +# 2. Install dependencies (cached unless package files change) +RUN --mount=type=cache,target=/root/.npm npm ci + +# 3. Copy source code last (changes frequently) +COPY . . + +# 4. Build application +RUN npm run build + +# ❌ BAD - Poor layer ordering +FROM node:24.11.1-alpine +WORKDIR /app +COPY . . # Changes frequently, invalidates cache +RUN npm install # Re-runs every time +RUN npm run build +``` + +**Best Practices:** + +- Copy dependency manifests (`package.json`, `package-lock.json`) before source code +- Use `npm ci` instead of `npm install` for reproducible installs +- Leverage BuildKit cache mounts: `--mount=type=cache,target=/root/.npm` +- Combine related RUN commands to reduce layers +- Use `.dockerignore` to exclude unnecessary files + +--- + +## Security Best Practices + +### 1. Minimize Attack Surface + +```dockerfile +# βœ… Remove unnecessary packages +RUN apk add --no-cache --virtual .build-deps \ + gcc \ + python3 \ + make && \ + npm install && \ + apk del .build-deps # Remove build dependencies +``` + +### 2. Use Specific COPY Instead of Wildcards + +```dockerfile +# βœ… GOOD - Explicit files +COPY package.json package-lock.json ./ + +# ❌ BAD - Wildcards can include unexpected files +COPY * ./ +``` + +### 3. Set Environment Variables Securely + +```dockerfile +# βœ… GOOD - Use ARG for build-time, ENV for runtime +ARG BUILD_VERSION +ENV NODE_ENV=production +ENV APP_VERSION=${BUILD_VERSION} + +# ❌ BAD - Don't hardcode secrets +ENV API_KEY=secret123 +``` + +### 4. Scan Images Regularly + +- Use `docker scout` or `trivy` to scan for vulnerabilities +- Update base images regularly +- Pin versions to control when updates happen + +### 5. Use Secrets Management + +```dockerfile +# βœ… GOOD - Use Docker secrets or environment variables +# Don't commit secrets to Dockerfile +RUN --mount=type=secret,id=api_key \ + API_KEY=$(cat /run/secrets/api_key) npm run build +``` + +--- + +## Docker Compose Best Practices + +### βœ… DO: Follow These Practices + +```yaml +# βœ… GOOD - Proper compose.yaml structure +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + NODE_VERSION: 24.11.1-alpine + image: myapp:1.0.0 + container_name: myapp-prod # Unique, descriptive names + ports: + - "8080:8080" + environment: + - NODE_ENV=production + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - app-network + +networks: + app-network: + driver: bridge +``` + +**Best Practices:** + +- Use unique, descriptive container names +- Pin image versions in production +- Add healthchecks for critical services +- Use named networks for service isolation +- Set appropriate restart policies +- Use environment files for sensitive data +- Avoid using `latest` tag in production + +### Common Compose Mistakes: + +```yaml +# ❌ BAD - Container name conflicts +services: + app1: + container_name: myapp + app2: + container_name: myapp # Conflict! + +# ❌ BAD - Using latest tag +services: + app: + image: node:latest + +# ❌ BAD - Exposing all ports +services: + app: + ports: + - "3000-9000:3000-9000" # Too broad +``` + +--- + +## .dockerignore Best Practices + +### βœ… DO: Exclude Unnecessary Files + +```dockerignore +# Dependencies (will be installed in container) +node_modules +npm-debug.log +yarn-error.log + +# Build outputs (will be built in container) +dist +build +.next +out + +# Development files +.env.local +.env.development +.env.test +*.log + +# Version control +.git +.gitignore +.gitattributes + +# IDE files +.vscode +.idea +*.swp +*.swo + +# Docker files (don't copy into image) +Dockerfile* +docker-compose*.yml +.dockerignore + +# CI/CD +.github +.gitlab-ci.yml +Jenkinsfile + +# Documentation +*.md +docs/ +README.md + +# Test files (if not needed in production) +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +coverage/ + +# Cache directories +.cache +.parcel-cache +.eslintcache +``` + +**Why?** + +- Reduces build context size +- Faster builds +- Prevents accidentally copying secrets +- Excludes unnecessary files from image + +--- + +## Common Mistakes to Avoid + +### 1. ❌ Using `latest` Tag + +```dockerfile +# ❌ BAD +FROM node:latest + +# βœ… GOOD +FROM node:24.11.1-alpine +``` + +### 2. ❌ Running as Root + +```dockerfile +# ❌ BAD +FROM node:24.11.1-alpine +COPY . . +CMD ["node", "server.js"] + +# βœ… GOOD +FROM node:24.11.1-alpine +COPY --chown=node:node . . +USER node +CMD ["node", "server.js"] +``` + +### 3. ❌ Including Dev Dependencies in Production + +```dockerfile +# ❌ BAD +RUN npm install + +# βœ… GOOD +RUN npm ci --only=production +# Or use multi-stage build +``` + +### 4. ❌ Poor Layer Caching + +```dockerfile +# ❌ BAD +COPY . . +RUN npm install + +# βœ… GOOD +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +``` + +### 5. ❌ Not Using Multi-Stage Builds + +```dockerfile +# ❌ BAD - Single stage with all tools +FROM node:24.11.1-alpine +RUN npm install +RUN npm run build +CMD ["node", "server.js"] + +# βœ… GOOD - Multi-stage +FROM node:24.11.1-alpine AS builder +RUN npm ci && npm run build + +FROM node:24.11.1-alpine +COPY --from=builder /app/dist ./dist +RUN npm ci --only=production +CMD ["node", "server.js"] +``` + +### 6. ❌ Copying Everything + +```dockerfile +# ❌ BAD +COPY . . + +# βœ… GOOD +COPY package.json package-lock.json ./ +COPY src ./src +COPY public ./public +``` + +### 7. ❌ Not Using BuildKit Cache Mounts + +```dockerfile +# ❌ BAD +RUN npm install # Downloads every time + +# βœ… GOOD +RUN --mount=type=cache,target=/root/.npm npm ci +``` + +### 8. ❌ Hardcoding Secrets + +```dockerfile +# ❌ BAD +ENV API_KEY=secret123 +ENV DATABASE_PASSWORD=password + +# βœ… GOOD +# Use Docker secrets, environment variables, or secret management +``` + +--- + +## Checklist for Dockerfiles + +Before finalizing a Dockerfile, ensure: + +- [ ] Base image version is pinned (no `latest`) +- [ ] Using Alpine or Slim variant +- [ ] Multi-stage build for production images +- [ ] Running as non-root user +- [ ] Layer ordering optimized for caching +- [ ] Using `npm ci` instead of `npm install` +- [ ] BuildKit cache mounts for package managers +- [ ] `.dockerignore` file is comprehensive +- [ ] No secrets hardcoded in Dockerfile +- [ ] Healthcheck defined (if applicable) +- [ ] Appropriate labels added +- [ ] Image scanned for vulnerabilities + +--- + +## Example: Production-Ready Dockerfile + +```dockerfile +# ========================================= +# Stage 1: Build +# ========================================= +ARG NODE_VERSION=24.11.1-alpine + +FROM node:${NODE_VERSION} AS builder + +WORKDIR /app + +# Copy dependency files first for better caching +COPY package.json package-lock.json ./ + +# Install dependencies with cache mount +RUN --mount=type=cache,target=/root/.npm \ + npm ci + +# Copy source code +COPY . . + +# Build application +RUN npm run build + +# ========================================= +# Stage 2: Runtime +# ========================================= +FROM node:${NODE_VERSION} AS runner + +WORKDIR /app + +# Set production environment +ENV NODE_ENV=production + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 + +# Copy built application and production dependencies +COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist +COPY --from=builder --chown=nodejs:nodejs /app/package.json ./ +COPY --from=builder --chown=nodejs:nodejs /app/package-lock.json ./ + +# Install only production dependencies +RUN --mount=type=cache,target=/root/.npm \ + npm ci --only=production && \ + npm cache clean --force + +# Switch to non-root user +USER nodejs + +# Expose port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:8080/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +# Start application +CMD ["node", "server.js"] +``` + +--- + +## Additional Resources + +- [Dockerfile Best Practices (Official)](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) +- [OWASP Docker Security](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html) +- [Docker Scout Documentation](https://docs.docker.com/scout/) +- [Multi-stage builds guide](https://docs.docker.com/build/building/multi-stage/) + +--- + +**Last Updated:** 2025 +**Maintained by:** Kristiyan Velkov diff --git a/.ai/rules b/.ai/rules new file mode 100644 index 0000000..7cd3eb4 --- /dev/null +++ b/.ai/rules @@ -0,0 +1,136 @@ +# Docker Best Practices Rules + +When creating or modifying Dockerfiles, Docker Compose files, or Docker-related configurations, follow these rules: + +## Image Version Pinning + +- **ALWAYS** pin exact versions of base images. Never use `latest` or floating tags. +- Use ARG variables for versions to allow override while maintaining defaults. +- Example: `ARG NODE_VERSION=24.11.1-alpine` (not `node:latest` or `node:24-alpine`) + +## Base Image Selection + +- **ALWAYS** use Alpine (`-alpine`) or Slim (`-slim`) variants for smaller images and reduced attack surface. +- Prefer Alpine for Node.js applications unless glibc compatibility is required. +- Example: `FROM node:24.11.1-alpine` (not `FROM node:24.11.1`) + +## Multi-Stage Builds + +- **ALWAYS** use multi-stage builds for production Dockerfiles to separate build and runtime dependencies. +- Name stages descriptively: `builder`, `runner`, `dev`, `test`, `final`. +- Copy only necessary artifacts between stages using `--from=`. +- Development Dockerfiles may use single-stage, but production must use multi-stage. + +## Non-Root User + +- **ALWAYS** run containers as a non-root user for security. +- Use built-in users when available (`node`, `nginx`). +- When creating custom users, use fixed UIDs/GIDs: `addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001` +- Use `--chown` flag when copying files: `COPY --chown=node:node` +- Set `USER` directive before `CMD`/`ENTRYPOINT`. + +## Layer Caching Optimization + +- **ALWAYS** copy dependency manifests (`package.json`, `package-lock.json`) before source code. +- Use `npm ci` instead of `npm install` for reproducible installs in production. +- Use BuildKit cache mounts: `RUN --mount=type=cache,target=/root/.npm npm ci` +- Order instructions from least to most frequently changing. + +## Security Best Practices + +- Never hardcode secrets in Dockerfiles. Use Docker secrets, environment variables, or secret management. +- Remove build dependencies after installation when possible. +- Use specific `COPY` commands instead of wildcards. +- Set `ENV NODE_ENV=production` in production images. +- Scan images regularly for vulnerabilities. + +## Docker Compose Best Practices + +- Use unique, descriptive container names. Never reuse container names across services. +- Pin image versions in production environments. +- Add healthchecks for critical services. +- Use named networks for service isolation. +- Set appropriate restart policies (`unless-stopped` for production). +- Avoid using `latest` tag in production. + +## Package Installation + +- Use `npm ci` for production builds (faster, more reliable, respects lock file). +- Use `npm install` only in development Dockerfiles. +- For global packages, pin exact versions: `npm install -g serve@14.2.5` +- Use `--only=production` flag when installing production dependencies: `npm ci --only=production` + +## File Ownership + +- Always set proper ownership when copying files: `COPY --chown=user:group` +- Change ownership before switching users: `RUN chown -R user:group /app` then `USER user` +- Ensure non-root users can access necessary directories. + +## .dockerignore + +- Maintain a comprehensive `.dockerignore` file to exclude: + - `node_modules`, build outputs (`dist`, `build`), test files, IDE files + - Docker files themselves, CI/CD configs, documentation + - Environment files, logs, cache directories + +## Code Style + +- Use clear section comments with separators: `# =========================================` +- Add descriptive comments explaining why, not just what. +- Keep ARG declarations at the top of the Dockerfile. +- Group related instructions together. + +## Common Mistakes to Avoid + +- ❌ Never use `latest` tag +- ❌ Never run as root user +- ❌ Never include devDependencies in production images +- ❌ Never copy everything with `COPY . .` before installing dependencies +- ❌ Never hardcode secrets +- ❌ Never skip multi-stage builds for production +- ❌ Never use `ADD` instead of `COPY` +- ❌ Never forget to set `NODE_ENV=production` in production images + +## Project-Specific Standards + +- Node.js version: Use `24.11.1-alpine` as the default. +- Nginx version: Use `nginxinc/nginx-unprivileged:alpine3.22` for unprivileged nginx. +- Serve package: Pin to `serve@14.2.5` when using Vercel serve. +- Ports: Use `8080` for production, `5173` for Vite dev server. +- Working directory: Use `/app` as the standard working directory. + +## When Creating New Dockerfiles + +1. Start with ARG declarations for versions +2. Use multi-stage build structure +3. Copy dependency files first +4. Install dependencies with cache mounts +5. Copy source code +6. Build application +7. Create/use non-root user +8. Set proper ownership +9. Switch to non-root user +10. Expose ports +11. Set CMD/ENTRYPOINT + +## Validation Checklist + +Before finalizing any Dockerfile, ensure: + +- [ ] Base image version is pinned (no `latest`) +- [ ] Using Alpine or Slim variant +- [ ] Multi-stage build for production images +- [ ] Running as non-root user +- [ ] Layer ordering optimized for caching +- [ ] Using `npm ci` instead of `npm install` (production) +- [ ] BuildKit cache mounts for package managers +- [ ] `.dockerignore` is comprehensive +- [ ] No secrets hardcoded +- [ ] Proper file ownership set +- [ ] `NODE_ENV=production` set (production images) + +## References + +- See `AGENTS.md` for detailed Docker best practices documentation +- Official Docker best practices: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ +- OWASP Docker Security: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html diff --git a/Dockerfile b/Dockerfile index 4d74453..9b13bda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,15 +28,15 @@ RUN npm run build FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner -# Use a built-in non-root user for security best practices -USER nginx - # Copy custom Nginx config COPY nginx.conf /etc/nginx/nginx.conf # Copy the static build output from the build stage to Nginx's default HTML serving directory COPY --chown=nginx:root --from=builder /app/dist /usr/share/nginx/html +# Use a built-in non-root user for security best practices +USER nginx + # Expose port 8080 to allow HTTP traffic # Note: The default NGINX container now listens on port 8080 instead of 80 EXPOSE 8080 diff --git a/Dockerfile.dev b/Dockerfile.dev index cbeb8f5..87a0d3c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,9 +1,9 @@ # ========================================= # Stage 1: Develop the React.js Application # ========================================= -ARG NODE_VERSION=24.11.0-alpine +ARG NODE_VERSION=24.11.1-alpine -# Use a lightweight Node.js image for development +# Use a lightweight Node.js Alpine image for development FROM node:${NODE_VERSION} AS dev # Set the working directory inside the container @@ -12,12 +12,22 @@ WORKDIR /app # Copy package-related files first to leverage Docker's caching mechanism COPY package.json package-lock.json ./ -# Install project dependencies +# Install project dependencies using BuildKit cache mount RUN --mount=type=cache,target=/root/.npm npm install # Copy the rest of the application source code into the container COPY . . +# Create a non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 + +# Change ownership of the app directory to the non-root user +RUN chown -R nodejs:nodejs /app + +# Switch to non-root user +USER nodejs + # Expose the port used by the Vite development server EXPOSE 5173 diff --git a/Dockerfile.serve b/Dockerfile.serve index 04b394d..d0aa390 100644 --- a/Dockerfile.serve +++ b/Dockerfile.serve @@ -34,16 +34,16 @@ ENV NODE_ENV=production WORKDIR /app # Install the Vercel "serve" package globally to serve static files -RUN npm install -g serve +RUN npm install -g serve@14.2.5 + +# Copy the built production files from the build stage +COPY --chown=node:node --from=build /app/dist /app/dist # Drop root privileges and run as the non-root "node" user USER node -# Copy the built production files from the build stage -COPY --from=build /app/dist /app/build - # Expose the port where the static server will run EXPOSE 8080 # Start the static server to serve the built React app -CMD ["serve", "-s", "build", "-l", "8080"] +CMD ["serve", "-s", "dist", "-l", "8080"] diff --git a/compose.yaml b/compose.yaml index b308a59..a3b6878 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,7 @@ services: context: . dockerfile: Dockerfile.dev image: docker-reactjs-sample-dev - container_name: reactjs-sample-app-prod + container_name: reactjs-sample-app-dev ports: - "5173:5173" develop: From b97c22a0b76fddd4894ea27f110fc9417f1fc40e Mon Sep 17 00:00:00 2001 From: "kristiyan.velkov" Date: Wed, 26 Nov 2025 08:12:39 +0200 Subject: [PATCH 19/19] [feat] remove not used vite.svg logo --- README.md | 2 +- {public => images}/demo.png | Bin public/vite.svg | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) rename {public => images}/demo.png (100%) delete mode 100644 public/vite.svg diff --git a/README.md b/README.md index 63588c5..960df07 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Part of the [official Docker React.js sample](https://docs.docker.com/guides/rea ## πŸš€ Demo
- React.js Docker Sample Application Demo + React.js Docker Sample Application Demo
## ✨ Features diff --git a/public/demo.png b/images/demo.png similarity index 100% rename from public/demo.png rename to images/demo.png diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file