diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..db1558f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# Temporary folders +tmp/ +temp/ \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..cf9558f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,127 @@ +name: Deploy to Kubernetes + +on: + workflow_dispatch: + inputs: + environment: + description: 'Deployment environment' + required: true + default: 'production' + type: choice + options: + - production + - staging + image_tag: + description: 'Docker image tag (optional, defaults to commit SHA)' + required: false + type: string + +env: + APP_NAME: www-cialloo-com + REGISTRY: ${{ secrets.DOCKER_REGISTRY }} + IMAGE_NAME: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REPOSITORY }} + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: ${{ github.event.inputs.environment || 'production' }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 'latest' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linting + run: npm run lint + + - name: Build application + run: npm run build + + - name: Set image tag + id: image-tag + run: | + if [ -n "${{ github.event.inputs.image_tag }}" ]; then + echo "tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Log in to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Set up kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'v1.28.0' + + - name: Configure kubectl + run: | + mkdir -p $HOME/.kube + echo "${{ secrets.KUBECONFIG }}" | base64 -d > $HOME/.kube/config + chmod 600 $HOME/.kube/config + + - name: Verify cluster connection + run: kubectl cluster-info + + - name: Deploy to Kubernetes + run: | + # Replace placeholders in k8s manifests + sed -i "s|{{IMAGE_TAG}}|${{ steps.image-tag.outputs.tag }}|g" k8s/deployment.yaml + sed -i "s|{{IMAGE_NAME}}|${{ env.IMAGE_NAME }}|g" k8s/deployment.yaml + sed -i "s|{{ENVIRONMENT}}|${{ github.event.inputs.environment || 'production' }}|g" k8s/deployment.yaml + sed -i "s|{{ENVIRONMENT}}|${{ github.event.inputs.environment || 'production' }}|g" k8s/service.yaml + sed -i "s|{{ENVIRONMENT}}|${{ github.event.inputs.environment || 'production' }}|g" k8s/namespace.yaml + sed -i "s|{{ENVIRONMENT}}|${{ github.event.inputs.environment || 'production' }}|g" k8s/ingress.yaml + + # Apply manifests + kubectl apply -f k8s/namespace.yaml + kubectl apply -f k8s/deployment.yaml + kubectl apply -f k8s/service.yaml + kubectl apply -f k8s/ingress.yaml + + - name: Wait for deployment to be ready + run: | + kubectl rollout status deployment/${{ env.APP_NAME }} -n ${{ env.APP_NAME }}-${{ github.event.inputs.environment || 'production' }} --timeout=300s + + - name: Get deployment status + run: | + echo "### Deployment Status" >> $GITHUB_STEP_SUMMARY + kubectl get pods -n ${{ env.APP_NAME }}-${{ github.event.inputs.environment || 'production' }} -o wide >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + kubectl get services -n ${{ env.APP_NAME }}-${{ github.event.inputs.environment || 'production' }} >> $GITHUB_STEP_SUMMARY + + - name: Notify deployment success + if: success() + run: | + echo "✅ Deployment successful!" + echo "Image: ${{ env.IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }}" + echo "Environment: ${{ github.event.inputs.environment || 'production' }}" + + - name: Notify deployment failure + if: failure() + run: | + echo "❌ Deployment failed!" + kubectl describe pods -n ${{ env.APP_NAME }}-${{ github.event.inputs.environment || 'production' }} || true \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..03322a8 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,172 @@ +# Kubernetes Deployment Setup + +This document explains how to set up the GitHub Actions workflow for deploying your React application to a Kubernetes cluster. + +## Required GitHub Secrets + +You need to configure the following secrets in your GitHub repository settings (`Settings` → `Secrets and variables` → `Actions`): + +### Docker Registry Secrets +- **`DOCKER_REGISTRY`**: Your Docker registry URL (e.g., `docker.io`, `ghcr.io`, `your-registry.com`) +- **`DOCKER_REPOSITORY`**: Your Docker repository name (e.g., `username/www-cialloo-com`) +- **`DOCKER_USERNAME`**: Username for Docker registry authentication +- **`DOCKER_PASSWORD`**: Password or access token for Docker registry authentication + +### Kubernetes Secrets +- **`KUBECONFIG`**: Base64-encoded kubeconfig file for your Kubernetes cluster + +## Setting up GitHub Secrets + +### 1. Docker Registry Configuration + +#### For Docker Hub: +```bash +DOCKER_REGISTRY=docker.io +DOCKER_REPOSITORY=yourusername/www-cialloo-com +DOCKER_USERNAME=yourusername +DOCKER_PASSWORD=your-docker-hub-token +``` + +#### For GitHub Container Registry: +```bash +DOCKER_REGISTRY=ghcr.io +DOCKER_REPOSITORY=yourusername/www-cialloo-com +DOCKER_USERNAME=yourusername +DOCKER_PASSWORD=your-github-token +``` + +### 2. Kubernetes Configuration + +To get your base64-encoded kubeconfig: + +```bash +# Encode your kubeconfig file +cat ~/.kube/config | base64 -w 0 +``` + +Copy the output and paste it as the value for the `KUBECONFIG` secret. + +## GitHub Environments (Optional but Recommended) + +You can set up GitHub environments for better security and approval workflows: + +1. Go to `Settings` → `Environments` +2. Create environments: `production`, `staging` +3. Configure protection rules (e.g., required reviewers) +4. Add environment-specific secrets if needed + +## Kubernetes Cluster Requirements + +Your Kubernetes cluster should have the following components: + +### 1. Traefik Ingress Controller +```bash +# Install Traefik using Helm +helm repo add traefik https://traefik.github.io/charts +helm repo update +helm install traefik traefik/traefik + +# Or using kubectl with CRDs +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +``` + +### 2. Cert-Manager (for SSL certificates) +```bash +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml +``` + +### 3. Docker Registry Secret +Create a secret for pulling images from your private registry: + +```bash +kubectl create secret docker-registry docker-registry-secret \ + --docker-server=your-registry.com \ + --docker-username=your-username \ + --docker-password=your-password \ + --docker-email=your-email@example.com \ + -n www-cialloo-com-production +``` + +## Customization + +### Update Domain Name +In `k8s/ingress.yaml`, replace `www.cialloo.com` with your actual domain: + +```yaml +spec: + tls: + - hosts: + - your-domain.com # Change this + secretName: www-cialloo-com-tls + rules: + - host: your-domain.com # Change this +``` + +### Adjust Resource Limits +In `k8s/deployment.yaml`, modify resource requests and limits based on your needs: + +```yaml +resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" +``` + +### Scaling +Adjust the number of replicas in `k8s/deployment.yaml`: + +```yaml +spec: + replicas: 2 # Change this number +``` + +## Manual Deployment + +To manually trigger the deployment: + +1. Go to your GitHub repository +2. Click on "Actions" tab +3. Select "Deploy to Kubernetes" workflow +4. Click "Run workflow" +5. Choose environment and optionally specify an image tag +6. Click "Run workflow" + +## Monitoring and Troubleshooting + +### Check deployment status: +```bash +kubectl get pods -n www-cialloo-com-production +kubectl get services -n www-cialloo-com-production +kubectl get ingress -n www-cialloo-com-production +``` + +### View logs: +```bash +kubectl logs -l app=www-cialloo-com -n www-cialloo-com-production +``` + +### Describe problematic pods: +```bash +kubectl describe pod -n www-cialloo-com-production +``` + +## Security Considerations + +1. **Least Privilege**: Ensure your kubeconfig has minimal required permissions +2. **Secret Rotation**: Regularly rotate Docker registry credentials and kubeconfig +3. **Environment Separation**: Use different namespaces/clusters for production and staging +4. **Network Policies**: Consider implementing Kubernetes network policies +5. **RBAC**: Configure proper Role-Based Access Control in your cluster + +## Workflow Features + +- **Manual Trigger Only**: Workflow only runs when manually dispatched +- **Environment Selection**: Choose between production and staging +- **Custom Image Tags**: Optionally specify custom Docker image tags +- **Health Checks**: Includes liveness and readiness probes +- **Rolling Updates**: Zero-downtime deployments +- **Status Reporting**: Detailed deployment status in GitHub Actions summary \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5f79c2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Build stage +FROM node:18-alpine as build + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM nginx:alpine + +# Copy built assets from build stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Expose port 80 +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..0aa3615 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: www-cialloo-com + namespace: www-cialloo-com-{{ENVIRONMENT}} + labels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} +spec: + replicas: 2 + selector: + matchLabels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} + template: + metadata: + labels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} + spec: + containers: + - name: www-cialloo-com + image: {{IMAGE_NAME}}:{{IMAGE_TAG}} + ports: + - containerPort: 80 + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + imagePullSecrets: + - name: docker-registry-secret \ No newline at end of file diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..fd0f916 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: www-cialloo-com-ingress + namespace: www-cialloo-com-{{ENVIRONMENT}} + labels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web +spec: + ingressClassName: traefik + rules: + - host: www.cialloo.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: www-cialloo-com-service + port: + number: 80 \ No newline at end of file diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..6d0b5b8 --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: www-cialloo-com-{{ENVIRONMENT}} + labels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} \ No newline at end of file diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..5dde889 --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: www-cialloo-com-service + namespace: www-cialloo-com-{{ENVIRONMENT}} + labels: + app: www-cialloo-com + environment: {{ENVIRONMENT}} +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + selector: + app: www-cialloo-com + environment: {{ENVIRONMENT}} \ No newline at end of file