Skip to content

Commit d6ba0f9

Browse files
committed
feat: add Dockerfile and GitHub Actions workflow for VPS deployment
1 parent 34cd592 commit d6ba0f9

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Contribution Manager App - VPS
2+
3+
on:
4+
push:
5+
branches: [ master, main ]
6+
paths:
7+
- 'src/**'
8+
- 'public/**'
9+
- 'package.json'
10+
- 'package-lock.json'
11+
- 'next.config.ts'
12+
- 'tailwind.config.ts'
13+
- 'tsconfig.json'
14+
- 'deploy/Dockerfile.c-m-app'
15+
- '.github/workflows/Contribution.Manager.VPS.yaml'
16+
17+
env:
18+
SERVICE_NAME: frontend
19+
IMAGE: ghcr.io/${{ github.repository_owner }}/contribution-manager-app
20+
DOCKERFILE: deploy/Dockerfile.c-m-app
21+
NODE_VERSION: '22.x'
22+
23+
permissions:
24+
contents: read
25+
packages: write
26+
27+
jobs:
28+
build-and-push:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
34+
- name: Set up QEMU (multiarch)
35+
uses: docker/setup-qemu-action@v3
36+
37+
- name: Set up Docker Buildx
38+
uses: docker/setup-buildx-action@v3
39+
40+
- name: Login to GHCR
41+
uses: docker/login-action@v3
42+
with:
43+
registry: ghcr.io
44+
username: ${{ github.actor }}
45+
password: ${{ secrets.GHCR_PAT }}
46+
47+
- name: Extract metadata
48+
id: meta
49+
uses: docker/metadata-action@v5
50+
with:
51+
images: ${{ env.IMAGE }}
52+
tags: |
53+
type=ref,event=branch
54+
type=ref,event=pr
55+
type=sha,prefix={{branch}}-
56+
type=raw,value=latest,enable={{is_default_branch}}
57+
58+
- name: Build and push Docker image
59+
uses: docker/build-push-action@v5
60+
with:
61+
context: .
62+
file: ${{ env.DOCKERFILE }}
63+
push: true
64+
tags: ${{ steps.meta.outputs.tags }}
65+
labels: ${{ steps.meta.outputs.labels }}
66+
platforms: linux/amd64,linux/arm64
67+
cache-from: type=gha
68+
cache-to: type=gha,mode=max
69+
build-args: |
70+
NODE_ENV=production
71+
NEXT_PUBLIC_FIREBASE_API_KEY=${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
72+
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}
73+
NEXT_PUBLIC_FIREBASE_PROJECT_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}
74+
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}
75+
NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE=${{ secrets.NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE }}
76+
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
77+
NEXT_PUBLIC_FIREBASE_APP_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
78+
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
79+
NEXT_PUBLIC_CONTRIBUTION_API_URL=${{ secrets.NEXT_PUBLIC_CONTRIBUTION_API_URL }}
80+
secrets: |
81+
google_credentials=${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
82+
83+
deploy:
84+
needs: build-and-push
85+
runs-on: ubuntu-latest
86+
steps:
87+
- name: Deploy to VPS
88+
uses: appleboy/[email protected]
89+
with:
90+
host: ${{ secrets.VPS_HOST }}
91+
username: ${{ secrets.VPS_USER }}
92+
key: ${{ secrets.SSH_PRIVATE_KEY }}
93+
script: |
94+
set -e
95+
echo "Deploying Manager App..."
96+
97+
# Login to GHCR
98+
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
99+
100+
# Navigate to app directory
101+
cd /srv/app || exit 1
102+
103+
# Create Google credentials file if it doesn't exist
104+
if [ ! -f "/srv/app/.secrets/google-credentials.json" ]; then
105+
mkdir -p /srv/app/.secrets
106+
echo "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}" | base64 --decode > /srv/app/.secrets/google-credentials.json
107+
chmod 600 /srv/app/.secrets/google-credentials.json
108+
fi
109+
110+
# Pull latest images
111+
docker compose pull ${{ env.SERVICE_NAME }}
112+
113+
# Restart the service
114+
docker compose up -d ${{ env.SERVICE_NAME }}
115+
116+
# Clean up old images
117+
docker image prune -f
118+
119+
echo "Manager App deployed successfully!"

deploy/Dockerfile.c-m-app

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Dockerfile for Contribution Manager App (Next.js)
2+
FROM node:22-alpine AS base
3+
4+
# Install dependencies only when needed
5+
FROM base AS deps
6+
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
7+
RUN apk add --no-cache libc6-compat curl
8+
WORKDIR /app
9+
10+
# Install dependencies based on the preferred package manager
11+
COPY package.json package-lock.json* ./
12+
RUN npm ci
13+
14+
# Rebuild the source code only when needed
15+
FROM base AS builder
16+
WORKDIR /app
17+
COPY --from=deps /app/node_modules ./node_modules
18+
COPY . .
19+
20+
# Build args for environment variables
21+
ARG NODE_ENV=production
22+
ARG NEXT_PUBLIC_FIREBASE_API_KEY
23+
ARG NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
24+
ARG NEXT_PUBLIC_FIREBASE_PROJECT_ID
25+
ARG NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
26+
ARG NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE
27+
ARG NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
28+
ARG NEXT_PUBLIC_FIREBASE_APP_ID
29+
ARG NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
30+
ARG NEXT_PUBLIC_CONTRIBUTION_API_URL
31+
32+
# Set environment variables for build
33+
ENV NODE_ENV=${NODE_ENV}
34+
ENV NEXT_PUBLIC_FIREBASE_API_KEY=${NEXT_PUBLIC_FIREBASE_API_KEY}
35+
ENV NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN}
36+
ENV NEXT_PUBLIC_FIREBASE_PROJECT_ID=${NEXT_PUBLIC_FIREBASE_PROJECT_ID}
37+
ENV NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET}
38+
ENV NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE=${NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE}
39+
ENV NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID}
40+
ENV NEXT_PUBLIC_FIREBASE_APP_ID=${NEXT_PUBLIC_FIREBASE_APP_ID}
41+
ENV NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID}
42+
ENV NEXT_PUBLIC_CONTRIBUTION_API_URL=${NEXT_PUBLIC_CONTRIBUTION_API_URL}
43+
44+
# Next.js collects completely anonymous telemetry data about general usage.
45+
# Learn more here: https://nextjs.org/telemetry
46+
# Uncomment the following line in case you want to disable telemetry during the build.
47+
ENV NEXT_TELEMETRY_DISABLED=1
48+
49+
RUN npm run build
50+
51+
# Production image, copy all the files and run next
52+
FROM base AS runner
53+
WORKDIR /app
54+
55+
ENV NODE_ENV=production
56+
ENV NEXT_TELEMETRY_DISABLED=1
57+
58+
RUN addgroup --system --gid 1001 nodejs
59+
RUN adduser --system --uid 1001 nextjs
60+
61+
# Create directories for Google credentials
62+
RUN mkdir -p /app/.secrets && chown -R nextjs:nodejs /app/.secrets
63+
64+
COPY --from=builder /app/public ./public
65+
66+
# Set the correct permission for prerender cache
67+
RUN mkdir .next
68+
RUN chown nextjs:nodejs .next
69+
70+
# Automatically leverage output traces to reduce image size
71+
# https://nextjs.org/docs/advanced-features/output-file-tracing
72+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
73+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
74+
75+
USER nextjs
76+
77+
EXPOSE 3000
78+
79+
ENV PORT=3000
80+
ENV HOSTNAME="0.0.0.0"
81+
82+
# Health check
83+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
84+
CMD curl -f http://localhost:3000/api/health || exit 1
85+
86+
CMD ["node", "server.js"]

0 commit comments

Comments
 (0)