INIT(deploy): portfolio project setup

Add complete portfolio project including:
- Next.js application in services/nextjs
- Docker configurations for dev and prod
- Kubernetes deployment manifests with kustomize
- ArgoCD application configuration
- GitHub Actions workflow for automated builds
This commit is contained in:
2025-11-23 23:40:15 +09:00
commit 95584b666a
18 changed files with 835 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: portfolio
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/Mayne0213/portfolio.git
targetRevision: main
path: deploy/k8s/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: portfolio
syncPolicy:
automated:
prune: true # 매니페스트에서 제거된 리소스 자동 삭제
selfHeal: true # 클러스터에서 수동 변경 시 자동 복구
allowEmpty: false
syncOptions:
- CreateNamespace=true # namespace가 없으면 자동 생성
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10

View File

@@ -0,0 +1,26 @@
# Development Dockerfile for Portfolio Next.js application
FROM node:20-alpine AS base
# Install dependencies for development
RUN apk add --no-cache libc6-compat curl
WORKDIR /app
# Copy package files
COPY package.json package-lock.json* ./
# Install all dependencies (including dev dependencies)
RUN npm ci
# Copy source code
COPY . .
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000 || exit 1
# Default command (can be overridden in docker-compose)
CMD ["npm", "run", "dev"]

View File

@@ -0,0 +1,56 @@
# Multi-stage build for Portfolio Next.js application
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat curl
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build the application
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
RUN apk add --no-cache curl
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
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
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000 || exit 1
CMD ["node", "server.js"]

View File

@@ -0,0 +1,27 @@
services:
# Development Portfolio Next.js Application
app:
build:
context: ../../services/nextjs
dockerfile: ../../deploy/docker/Dockerfile.dev
container_name: portfolio-app-dev
restart: unless-stopped
labels:
kompose.namespace: portfolio
ports:
- 3005:3000
environment:
- NODE_ENV=development
- WATCHPACK_POLLING=true
networks:
- portfolio-network
volumes:
- ../../services/nextjs:/app
- /app/node_modules
- /app/.next
command: npm run dev
networks:
portfolio-network:
driver: bridge
name: portfolio-network-dev

View File

@@ -0,0 +1,22 @@
services:
# Production Portfolio Next.js Application
app:
image: portfolio-app
build:
context: ../../services/nextjs
dockerfile: ../../deploy/docker/Dockerfile.prod
container_name: portfolio-app-prod
restart: unless-stopped
labels:
kompose.namespace: portfolio
ports:
- 3005:3000
environment:
- NODE_ENV=production
networks:
- portfolio-network
networks:
portfolio-network:
driver: bridge
name: portfolio-network-prod

View File

@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: portfolio-app
labels:
app: portfolio-app
spec:
replicas: 1
selector:
matchLabels:
app: portfolio-app
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: portfolio-app
spec:
containers:
- name: portfolio-app
image: ghcr.io/mayne0213/portfolio:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
protocol: TCP
env:
- name: NODE_ENV
value: production
resources:
requests:
memory: "100Mi"
cpu: "50m"
limits:
memory: "200Mi"
cpu: "150m"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
restartPolicy: Always

View File

@@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/component: web
images:
- name: ghcr.io/mayne0213/portfolio
newTag: latest

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: portfolio-service
labels:
app: portfolio-app
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
selector:
app: portfolio-app

View File

@@ -0,0 +1,19 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: portfolio-app
labels:
environment: production
spec:
replicas: 1
template:
spec:
containers:
- name: portfolio-app
resources:
requests:
memory: "100Mi"
cpu: "50m"
limits:
memory: "200Mi"
cpu: "150m"

View File

@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: portfolio
resources:
- ../../base
commonLabels:
environment: production
# 이미지 태그 설정
images:
- name: ghcr.io/mayne0213/portfolio
newTag: latest
patchesStrategicMerge:
- deployment-patch.yaml