diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0709f34 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,130 @@ +name: CI/CD Pipeline - Frontend + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_STARKNET_CHAIN_ID=${{ secrets.NEXT_PUBLIC_STARKNET_CHAIN_ID }} + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + name: Deploy to EC2 + runs-on: ubuntu-latest + needs: build-and-push + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Deploy to EC2 + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + script: | + cd /home/ec2-user + + echo "=== Starting Frontend Deployment ===" + + # Login to GitHub Container Registry + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + # Pull latest image + echo "Pulling latest frontend image..." + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + # Check if running via PM2 (legacy setup) + if pm2 list | grep -q "autoswappr-dapp"; then + echo "Detected PM2 process - migrating to Docker..." + pm2 stop autoswappr-dapp || true + pm2 delete autoswappr-dapp || true + pm2 save --force + echo "✓ PM2 process stopped and removed" + fi + + # Stop and remove old Docker container if exists + if docker ps -a --format '{{.Names}}' | grep -q "^autoswappr-dapp$"; then + echo "Stopping existing Docker container..." + docker stop autoswappr-dapp || true + docker rm autoswappr-dapp || true + fi + + # Run new container + echo "Starting new frontend container..." + docker run -d \ + --name autoswappr-dapp \ + --restart unless-stopped \ + -p 3000:3000 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + # Wait for container to be healthy + echo "Waiting for frontend to be healthy..." + sleep 5 + + # Health check + for i in {1..30}; do + if curl -f http://localhost:3000 >/dev/null 2>&1; then + echo "✓ Frontend is healthy!" + break + fi + if [ $i -eq 30 ]; then + echo "✗ Frontend health check failed!" + docker logs autoswappr-dapp --tail 50 + exit 1 + fi + echo "Waiting for frontend... ($i/30)" + sleep 2 + done + + # Clean up old images + docker image prune -af + + echo "=== Frontend Deployment Completed Successfully! ===" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7451952 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +# Multi-stage build for Next.js frontend +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy package files +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Set build-time environment variables +ARG NEXT_PUBLIC_API_URL +ARG NEXT_PUBLIC_STARKNET_CHAIN_ID + +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +ENV NEXT_PUBLIC_STARKNET_CHAIN_ID=${NEXT_PUBLIC_STARKNET_CHAIN_ID} + +# Install pnpm +RUN npm install -g pnpm + +# Build the application +RUN pnpm build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy necessary files +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/next.config.ts b/next.config.ts index e9ffa30..f228c0e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + output: 'standalone', // Required for Docker deployment }; export default nextConfig;