Skip to content

Commit cdff62a

Browse files
adity1rautclaude
andcommitted
feat: add Docker support and professional CI/CD pipeline
- Add multi-stage Dockerfiles for backend (Node 20 Alpine) and AI service (Python 3.11-slim) - Both images run as non-root users with health checks - Add docker-compose.yml to orchestrate both services with internal networking - Add /health endpoint to backend for Docker/LB health checks - Fix GitHub Actions folder: move workflow/ → workflows/ (singular is ignored by GitHub) - Replace basic CI with 6-job pipeline: frontend lint+build, backend vitest, security audit (npm audit + pip-audit), Docker image validation, Docker Hub publish on master push Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7ac8bcb commit cdff62a

9 files changed

Lines changed: 412 additions & 46 deletions

File tree

.github/workflow/ci.yaml

Lines changed: 0 additions & 45 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
name: CI / CD — ChatConnect
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
# ─── Jobs ─────────────────────────────────────────────────────────────────────
14+
jobs:
15+
16+
# ────────────────────────────────────────────────────────────────────────────
17+
# 1. Lint + Test — Frontend (React/Vite)
18+
# ────────────────────────────────────────────────────────────────────────────
19+
frontend-lint-build:
20+
name: Frontend — Lint & Build
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Node.js 20
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: 20
31+
cache: npm
32+
cache-dependency-path: client/package-lock.json
33+
34+
- name: Install dependencies
35+
working-directory: client
36+
run: npm ci
37+
38+
- name: Run ESLint
39+
working-directory: client
40+
run: npm run lint
41+
42+
- name: Build
43+
working-directory: client
44+
run: npm run build
45+
46+
- name: Upload build artifact
47+
uses: actions/upload-artifact@v4
48+
with:
49+
name: frontend-dist
50+
path: client/dist
51+
retention-days: 7
52+
53+
# ────────────────────────────────────────────────────────────────────────────
54+
# 2. Test — Backend (Node.js / Vitest)
55+
# ────────────────────────────────────────────────────────────────────────────
56+
backend-test:
57+
name: Backend — Unit Tests
58+
runs-on: ubuntu-latest
59+
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v4
63+
64+
- name: Setup Node.js 20
65+
uses: actions/setup-node@v4
66+
with:
67+
node-version: 20
68+
cache: npm
69+
cache-dependency-path: backend/package-lock.json
70+
71+
- name: Install dependencies
72+
working-directory: backend
73+
run: npm ci
74+
75+
- name: Run Vitest
76+
working-directory: backend
77+
run: npm test
78+
env:
79+
NODE_ENV: test
80+
JWT_SECRET: test-jwt-secret
81+
JWT_REFRESH_SECRET: test-refresh-secret
82+
MONGO_URI: ${{ secrets.MONGO_URI_TEST || 'mongodb://localhost:27017/test' }}
83+
84+
# ────────────────────────────────────────────────────────────────────────────
85+
# 3. Security — npm audit + pip-audit
86+
# ────────────────────────────────────────────────────────────────────────────
87+
security-audit:
88+
name: Security Audit
89+
runs-on: ubuntu-latest
90+
91+
steps:
92+
- name: Checkout
93+
uses: actions/checkout@v4
94+
95+
- name: Setup Node.js 20
96+
uses: actions/setup-node@v4
97+
with:
98+
node-version: 20
99+
cache: npm
100+
cache-dependency-path: backend/package-lock.json
101+
102+
- name: npm audit (backend)
103+
working-directory: backend
104+
run: npm audit --audit-level=high
105+
continue-on-error: true # report but don't block
106+
107+
- name: npm audit (frontend)
108+
working-directory: client
109+
run: npm audit --audit-level=high
110+
continue-on-error: true
111+
112+
- name: Setup Python
113+
uses: actions/setup-python@v5
114+
with:
115+
python-version: "3.11"
116+
117+
- name: pip-audit (AI service)
118+
working-directory: server
119+
run: |
120+
pip install pip-audit --quiet
121+
pip-audit -r requirements.txt --skip-editable
122+
continue-on-error: true
123+
124+
# ────────────────────────────────────────────────────────────────────────────
125+
# 4. Docker build validation — backend image
126+
# ────────────────────────────────────────────────────────────────────────────
127+
docker-backend:
128+
name: Docker — Build Backend Image
129+
runs-on: ubuntu-latest
130+
needs: [backend-test]
131+
132+
steps:
133+
- name: Checkout
134+
uses: actions/checkout@v4
135+
136+
- name: Set up Docker Buildx
137+
uses: docker/setup-buildx-action@v3
138+
139+
- name: Build backend image (no push)
140+
uses: docker/build-push-action@v6
141+
with:
142+
context: ./backend
143+
file: ./backend/Dockerfile
144+
push: false
145+
tags: chatconnect-backend:ci
146+
cache-from: type=gha
147+
cache-to: type=gha,mode=max
148+
149+
# ────────────────────────────────────────────────────────────────────────────
150+
# 5. Docker build validation — AI service image
151+
# ────────────────────────────────────────────────────────────────────────────
152+
docker-ai-service:
153+
name: Docker — Build AI Service Image
154+
runs-on: ubuntu-latest
155+
156+
steps:
157+
- name: Checkout
158+
uses: actions/checkout@v4
159+
160+
- name: Set up Docker Buildx
161+
uses: docker/setup-buildx-action@v3
162+
163+
- name: Build AI service image (no push)
164+
uses: docker/build-push-action@v6
165+
with:
166+
context: ./server
167+
file: ./server/Dockerfile
168+
push: false
169+
tags: chatconnect-ai:ci
170+
cache-from: type=gha
171+
cache-to: type=gha,mode=max
172+
173+
# ────────────────────────────────────────────────────────────────────────────
174+
# 6. Push to Docker Hub — only on master push, after all checks pass
175+
# ────────────────────────────────────────────────────────────────────────────
176+
docker-publish:
177+
name: Docker — Publish to Docker Hub
178+
runs-on: ubuntu-latest
179+
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
180+
needs: [frontend-lint-build, backend-test, docker-backend, docker-ai-service]
181+
182+
steps:
183+
- name: Checkout
184+
uses: actions/checkout@v4
185+
186+
- name: Set up Docker Buildx
187+
uses: docker/setup-buildx-action@v3
188+
189+
- name: Log in to Docker Hub
190+
uses: docker/login-action@v3
191+
with:
192+
username: ${{ secrets.DOCKERHUB_USERNAME }}
193+
password: ${{ secrets.DOCKERHUB_TOKEN }}
194+
195+
- name: Extract metadata (tags/labels)
196+
id: meta-backend
197+
uses: docker/metadata-action@v5
198+
with:
199+
images: ${{ secrets.DOCKERHUB_USERNAME }}/chatconnect-backend
200+
tags: |
201+
type=sha,prefix=sha-,format=short
202+
type=raw,value=latest
203+
204+
- name: Push backend image
205+
uses: docker/build-push-action@v6
206+
with:
207+
context: ./backend
208+
file: ./backend/Dockerfile
209+
push: true
210+
tags: ${{ steps.meta-backend.outputs.tags }}
211+
labels: ${{ steps.meta-backend.outputs.labels }}
212+
cache-from: type=gha
213+
cache-to: type=gha,mode=max
214+
215+
- name: Extract metadata — AI service
216+
id: meta-ai
217+
uses: docker/metadata-action@v5
218+
with:
219+
images: ${{ secrets.DOCKERHUB_USERNAME }}/chatconnect-ai
220+
tags: |
221+
type=sha,prefix=sha-,format=short
222+
type=raw,value=latest
223+
224+
- name: Push AI service image
225+
uses: docker/build-push-action@v6
226+
with:
227+
context: ./server
228+
file: ./server/Dockerfile
229+
push: true
230+
tags: ${{ steps.meta-ai.outputs.tags }}
231+
labels: ${{ steps.meta-ai.outputs.labels }}
232+
cache-from: type=gha
233+
cache-to: type=gha,mode=max

backend/.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
node_modules
2+
npm-debug.log*
3+
.env
4+
.env.*
5+
!.env.example
6+
tests
7+
*.test.js
8+
.git
9+
.gitignore
10+
Dockerfile
11+
.dockerignore
12+
vercel.json

backend/Dockerfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# ─── Stage 1: deps ────────────────────────────────────────────────────────────
2+
FROM node:20-alpine AS deps
3+
4+
WORKDIR /app
5+
6+
# Install only production deps first (layer cache)
7+
COPY package.json package-lock.json ./
8+
RUN npm ci --omit=dev
9+
10+
# ─── Stage 2: runner ──────────────────────────────────────────────────────────
11+
FROM node:20-alpine AS runner
12+
13+
WORKDIR /app
14+
15+
# Security: run as non-root
16+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
17+
18+
# Copy production node_modules from deps stage
19+
COPY --from=deps /app/node_modules ./node_modules
20+
21+
# Copy source
22+
COPY --chown=appuser:appgroup . .
23+
24+
# Remove dev/test files not needed at runtime
25+
RUN rm -rf tests .env.example
26+
27+
ENV NODE_ENV=production
28+
ENV PORT=5000
29+
30+
EXPOSE 5000
31+
32+
USER appuser
33+
34+
# Healthcheck — hits the /health endpoint (add this route if missing)
35+
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
36+
CMD wget -qO- http://localhost:5000/health || exit 1
37+
38+
CMD ["node", "main.js"]

backend/main.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,15 @@ app.use("/api/friends", friendRoutes);
5555
app.use("/api/dashboard", dashboardRoutes);
5656
app.use("/api/ai", aiRoutes);
5757

58-
app.get("/", (req, res) => {
58+
app.get("/", (_req, res) => {
5959
res.status(200).json({ message: "Server is running" });
6060
});
6161

62+
// Docker / load-balancer healthcheck
63+
app.get("/health", (_req, res) => {
64+
res.status(200).json({ status: "ok", uptime: process.uptime() });
65+
});
66+
6267
const PORT = process.env.PORT || 5000;
6368

6469
httpServer.listen(PORT, () => {

0 commit comments

Comments
 (0)