Skip to content

Commit 5000adf

Browse files
authored
feat: Add CI/CD with Docker deployment and automated releases (#8)
1 parent 54a1000 commit 5000adf

File tree

10 files changed

+390
-0
lines changed

10 files changed

+390
-0
lines changed

.dockerignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# project specific
2+
data
3+
4+
# Git
5+
.git
6+
.github
7+
.gitignore
8+
9+
# Node
10+
node_modules
11+
12+
# Build output
13+
.output
14+
.nuxt
15+
16+
# IDE
17+
.vscode
18+
19+
# Docs
20+
docs
21+
22+
# Local files
23+
.env
24+
25+
# Log files
26+
pnpm-debug.log*
27+
28+
# Docker
29+
Dockerfile
30+
docker-compose.yml
31+
docker-build.sh
32+
.dockerignore

.github/workflows/release.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Release and Publish
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
release-please:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
outputs:
15+
release_created: ${{ steps.release.outputs.release_created }}
16+
tag_name: ${{ steps.release.outputs.tag_name }}
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Setup pnpm
22+
uses: pnpm/action-setup@v4
23+
with:
24+
version: 10
25+
26+
- name: Setup Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: '22'
30+
cache: 'pnpm'
31+
32+
- name: Release Please Action
33+
id: release
34+
uses: google-github-actions/release-please-action@v4
35+
with:
36+
token: ${{ secrets.GITHUB_TOKEN }}
37+
release-type: node
38+
39+
publish-docker-image:
40+
needs: release-please
41+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
42+
runs-on: ubuntu-latest
43+
permissions:
44+
contents: read
45+
packages: write
46+
steps:
47+
- name: Checkout repository
48+
uses: actions/checkout@v4
49+
with:
50+
fetch-depth: 0
51+
52+
- name: Log in to GitHub Container Registry
53+
uses: docker/login-action@v3
54+
with:
55+
registry: ghcr.io
56+
username: ${{ github.actor }}
57+
password: ${{ secrets.GITHUB_TOKEN }}
58+
59+
- name: Extract metadata (tags, labels) for Docker
60+
id: meta
61+
uses: docker/metadata-action@v5
62+
with:
63+
images: ghcr.io/${{ github.repository }}
64+
tags: |
65+
type=semver,pattern={{version}}
66+
type=semver,pattern={{major}}.{{minor}}
67+
type=semver,pattern={{major}}
68+
latest=auto
69+
70+
- name: Build and push Docker image
71+
uses: docker/build-push-action@v6
72+
with:
73+
context: .
74+
push: true
75+
tags: ${{ steps.meta.outputs.tags }}
76+
labels: ${{ steps.meta.outputs.labels }}

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "1.0.0"
3+
}

Dockerfile

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# --- Build Stage ---
2+
# This stage builds the application. It installs all dependencies (including dev)
3+
# and creates the optimized production output in the /.output directory.
4+
FROM node:22-alpine AS build
5+
6+
# Set working directory
7+
WORKDIR /app
8+
9+
# Install pnpm version 10
10+
# Enable corepack and activate pnpm
11+
RUN corepack enable
12+
RUN corepack prepare pnpm@10 --activate
13+
14+
# Copy package.json and pnpm-lock.yaml
15+
COPY package.json pnpm-lock.yaml ./
16+
17+
# Install dependencies with caching
18+
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile
19+
20+
# Copy the rest of the application source code
21+
COPY . .
22+
23+
# Build the application for production
24+
RUN pnpm build
25+
26+
# --- Production Stage ---
27+
# This is the final, minimal image. It only copies the built application
28+
# from the 'build' stage and installs production-only dependencies.
29+
# No source code or development dependencies are included.
30+
FROM node:22-alpine AS production
31+
32+
# Set working directory
33+
WORKDIR /app
34+
35+
# Enable corepack and activate pnpm in production stage
36+
RUN corepack enable
37+
RUN corepack prepare pnpm@10 --activate
38+
39+
# Copy the built output from the build stage
40+
COPY --from=build /app/.output ./.output
41+
42+
# Copy production dependencies
43+
COPY --from=build /app/package.json ./
44+
COPY --from=build /app/pnpm-lock.yaml ./
45+
RUN pnpm install --prod --frozen-lockfile
46+
47+
# Expose the port the app runs on
48+
EXPOSE 3000
49+
50+
# Set the command to start the application
51+
CMD ["node", ".output/server/index.mjs"]

docker-build.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
# Exit immediately if a command exits with a non-zero status.
4+
set -e
5+
6+
# Define variables
7+
IMAGE_NAME="stage-flow-tools"
8+
DOCKERFILE="Dockerfile"
9+
10+
# Build the Docker image
11+
echo "Building Docker image: $IMAGE_NAME..."
12+
docker build -t "$IMAGE_NAME" -f "$DOCKERFILE" .
13+
14+
echo "Docker image '$IMAGE_NAME' built successfully."

docker-compose.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: '3.9'
2+
3+
services:
4+
app:
5+
build:
6+
context: .
7+
dockerfile: Dockerfile
8+
image: stage-flow-tools
9+
container_name: stage-flow-tools
10+
restart: unless-stopped
11+
networks:
12+
- traefik-public
13+
volumes:
14+
- ./data:/app/data
15+
environment:
16+
- NUXT_JWT_SECRET=${NUXT_JWT_SECRET}
17+
labels:
18+
- "traefik.enable=true"
19+
- "traefik.http.routers.stage-flow-tools.rule=Host(`quiz.your-domain.com`)"
20+
- "traefik.http.routers.stage-flow-tools.entrypoints=websecure"
21+
- "traefik.http.services.stage-flow-tools.loadbalancer.server.port=3000"
22+
- "traefik.http.routers.stage-flow-tools.tls.certresolver=myresolver"
23+
24+
networks:
25+
traefik-public:
26+
external: true

docs/deployment-docker.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Production Deployment with Docker
2+
3+
This guide describes how to deploy the application to a production environment on a Linux server using Docker and Docker Compose.
4+
5+
## Prerequisites
6+
7+
- A Linux server with Docker and Docker Compose installed.
8+
- A Traefik reverse proxy instance running and configured on the same server.
9+
- The `traefik-public` Docker network must exist.
10+
- A domain or subdomain pointed to the server's IP address.
11+
12+
## Setup
13+
14+
### 1. Clone the Repository
15+
16+
Connect to your server and clone the repository:
17+
18+
```bash
19+
git clone <repository-url>
20+
cd stage-flow-tools
21+
```
22+
23+
### 2. Configure Environment
24+
25+
Copy the example environment file and customize it:
26+
27+
```bash
28+
cp .env.example .env
29+
```
30+
31+
Edit the `.env` file and set a strong, unique `NUXT_JWT_SECRET`. You can generate one with:
32+
33+
```bash
34+
openssl rand -base64 48
35+
```
36+
37+
### 3. Configure Docker Compose
38+
39+
Open the `docker-compose.yml` file and update the Traefik `Host` rule to match your domain:
40+
41+
```yaml
42+
services:
43+
app:
44+
# ...
45+
labels:
46+
- "traefik.http.routers.quiz-app.rule=Host(`quiz.your-domain.com`)" # Change this
47+
# ...
48+
```
49+
50+
### 4. Build and Start the Container
51+
52+
Build the Docker image and start the container in detached mode:
53+
54+
```bash
55+
docker compose up --build -d
56+
```
57+
58+
The application will be accessible at your configured domain. Traefik will automatically handle SSL certificate provisioning via Let's Encrypt.
59+
60+
## Data Persistence
61+
62+
The `docker-compose.yml` file is configured to mount the local `./data` directory into the container at `/app/data`. This ensures that all application data (questions, answers, etc.) is persisted on the host machine, even if the container is removed or recreated.
63+
64+
## Maintenance
65+
66+
### Updating the Application
67+
68+
To update the application to the latest version:
69+
70+
```bash
71+
git pull
72+
docker compose up --build -d
73+
```
74+
75+
### Viewing Logs
76+
77+
To view the application logs:
78+
79+
```bash
80+
docker compose logs -f
81+
```
82+
83+
### Stopping the Application
84+
85+
To stop the application:
86+
87+
```bash
88+
docker compose down

docs/local-testing.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Local Docker Testing
2+
3+
This guide provides minimal instructions for building and testing the Docker container locally. This process simulates the image creation step of the CI/CD workflow.
4+
5+
## 1. Build the Docker Image
6+
7+
Ensure the Docker daemon is running on your machine. Then, from the project root, execute the build script:
8+
9+
```bash
10+
./docker-build.sh
11+
```
12+
13+
This command builds the image and tags it as `stage-flow-tools`.
14+
15+
## 2. Run the Container
16+
17+
### Prerequisites
18+
19+
Before running the container, make sure you have a `.env` file in your project root. If you don't have one, create it by copying the example file:
20+
21+
```bash
22+
cp .env.example .env
23+
```
24+
25+
Ensure the `NUXT_JWT_SECRET` and other variables are set correctly in this file.
26+
27+
### Command
28+
29+
To test the built image, run the following command. It mounts the local `.env` file and the `data` directory into the container.
30+
31+
```bash
32+
docker run --rm -it \
33+
-p 3000:3000 \
34+
--env-file ./.env \
35+
-v "$(pwd)/data:/app/data" \
36+
--name test-stage-flow \
37+
stage-flow-tools
38+
```
39+
40+
The application will be available at `http://localhost:3000`. The container will be automatically removed when you stop it (`Ctrl+C`).
41+
42+
### A Note on Versioning
43+
44+
The local build script tags the image with `:latest`. The automated GitHub Actions workflow handles versioning for releases by automatically tagging the image with the correct semantic version (e.g., `1.2.3`, `1.2`, `1`) based on the release created by the `release-please` bot.

docs/release-flow.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Release Flow
2+
3+
This document outlines the automated release process for the application, which is managed by GitHub Actions and the `release-please` bot.
4+
5+
## Overview
6+
7+
The release process is triggered automatically when commits are pushed to the `main` branch. It handles versioning, changelog generation, GitHub releases, and Docker image publishing.
8+
9+
## Workflow
10+
11+
### 1. Development
12+
13+
Developers work on feature or fix branches and create pull requests to merge their changes into the `main` branch. Commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification.
14+
15+
### 2. Release Proposal
16+
17+
After one or more pull requests are merged into `main`, the `release-please` GitHub Action runs automatically. It analyzes the commit history since the last release and determines the next semantic version number.
18+
19+
If a new release is warranted, the bot creates a new pull request titled `chore(main): release [version]`. This PR includes:
20+
21+
- An updated `CHANGELOG.md` file.
22+
- The new version number bumped in `package.json`.
23+
24+
### 3. Human Approval
25+
26+
A project maintainer reviews the release pull request. If everything is correct, the PR is merged into the `main` branch. This merge action is the trigger for the next step.
27+
28+
### 4. Release and Publication
29+
30+
Upon merging the release PR, the GitHub Actions workflow performs the following tasks:
31+
32+
1. **Creates a GitHub Release**: A new release is created on GitHub with the tag `v[version]`. The release notes are populated from the `CHANGELOG.md`.
33+
2. **Builds Docker Image**: A new Docker image is built based on the state of the `main` branch at the release tag.
34+
3. **Pushes to Registry**: The Docker image is pushed to the GitHub Container Registry (`ghcr.io`) with tags corresponding to the release version (e.g., `1.2.3`, `1.2`, `1`, `latest`).
35+
36+
The `main` branch is now updated with the latest version, and a new, versioned Docker image is available for deployment.

0 commit comments

Comments
 (0)