Replace Gitea Actions with Jenkins CI/CD.
office.sejong.dev/pipeline/head There was a failure building this commit
office.sejong.dev/pipeline/head There was a failure building this commit
Build and push images from a Jenkinsfile, then deploy over SSH with compose on main. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b0a6fc326c
commit
f4f98704be
3 files changed
+145
-95
No files matched your search
@@ -1,84 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Type-check & Build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# CAD/OCR/3D viewer assets make Vite exceed the default ~2GB heap
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Type-check and build
|
||||
run: npm run build
|
||||
|
||||
image:
|
||||
name: Build & Push image
|
||||
needs: build
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Job image often has no docker binary; host sock may already be mounted by act_runner.
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz \
|
||||
| tar xz --strip-components=1 -C "$HOME/.local/bin" docker/docker
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
if [ -S /var/run/docker.sock ]; then
|
||||
docker info
|
||||
docker version
|
||||
exit 0
|
||||
fi
|
||||
echo "Host /var/run/docker.sock is not available in this job."
|
||||
echo "On act_runner, set container.docker_host: \"\" (use host Docker)"
|
||||
echo "and/or container.valid_volumes: [/var/run/docker.sock]"
|
||||
ls -la /var/run/docker.sock || true
|
||||
docker info || true
|
||||
exit 1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.sejong.dev
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
docker.sejong.dev/sejong-office:latest
|
||||
docker.sejong.dev/sejong-office:sha-${{ github.sha }}
|
||||
|
||||
- name: Log out of registry
|
||||
if: always()
|
||||
run: docker logout docker.sejong.dev
|
||||
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
NODE_OPTIONS = '--max-old-space-size=8192'
|
||||
NODE_IMAGE = 'node:22-bookworm'
|
||||
REGISTRY = 'docker.sejong.dev'
|
||||
IMAGE = 'docker.sejong.dev/sejong-office'
|
||||
SSH_CONFIG_NAME = 'sejong-office'
|
||||
DEPLOY_DIR = '/volume1/docker/sejong-office'
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Type-check & Build') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
docker run --rm \
|
||||
-u "$(id -u):$(id -g)" \
|
||||
-e HOME=/tmp \
|
||||
-e NODE_OPTIONS \
|
||||
-v "$PWD":/app \
|
||||
-w /app \
|
||||
"$NODE_IMAGE" \
|
||||
bash -lc 'npm ci && npm run build'
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Release') {
|
||||
when {
|
||||
allOf {
|
||||
not { changeRequest() }
|
||||
anyOf {
|
||||
branch 'main'
|
||||
expression {
|
||||
def branch = env.GIT_BRANCH ?: env.BRANCH_NAME ?: ''
|
||||
branch == 'main' || branch == 'origin/main' || branch == 'refs/heads/main'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Build & Push image') {
|
||||
steps {
|
||||
withCredentials([usernamePassword(
|
||||
credentialsId: 'docker-sejong-dev',
|
||||
usernameVariable: 'REGISTRY_USERNAME',
|
||||
passwordVariable: 'REGISTRY_PASSWORD'
|
||||
)]) {
|
||||
sh '''
|
||||
set -eu
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
|
||||
docker build -t "$IMAGE:latest" -t "$IMAGE:sha-${GIT_COMMIT}" .
|
||||
docker push "$IMAGE:latest"
|
||||
docker push "$IMAGE:sha-${GIT_COMMIT}"
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh 'docker logout "$REGISTRY" || true'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
sshPublisher(
|
||||
failOnError: true,
|
||||
publishers: [
|
||||
sshPublisherDesc(
|
||||
configName: env.SSH_CONFIG_NAME,
|
||||
verbose: true,
|
||||
transfers: [
|
||||
sshTransfer(
|
||||
execTimeout: 1800000,
|
||||
usePty: true,
|
||||
execCommand: """
|
||||
sudo -n su -c 'set -eu; cd "${env.DEPLOY_DIR}"; docker compose pull; docker compose up -d'
|
||||
""".stripIndent().trim()
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,27 +101,64 @@ volumes:
|
||||
documentserver_data:
|
||||
```
|
||||
|
||||
## CI (Gitea Actions)
|
||||
## CI/CD (Jenkins)
|
||||
|
||||
[`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) 이 `main` PR/push에서 `npm run build`를 돌리고, **`main` push**에서만 Docker 이미지를 레지스트리에 push합니다.
|
||||
[`Jenkinsfile`](Jenkinsfile) 이 `main` PR/브랜치에서 `npm ci` + `npm run build`를 돌리고, **`main` 브랜치 빌드**(PR 제외)에서만 이미지를 push한 뒤 서버에 SSH로 배포합니다.
|
||||
|
||||
에이전트에 Docker가 있어야 합니다. Node 22는 `node:22-bookworm` 컨테이너로 돌리며, Vite 힙 한도는 `NODE_OPTIONS=--max-old-space-size=8192`입니다.
|
||||
|
||||
푸시 태그: `docker.sejong.dev/sejong-office:latest`, `docker.sejong.dev/sejong-office:sha-<commit>`
|
||||
|
||||
### 레포 Actions Secrets
|
||||
배포는 [Publish Over SSH](https://plugins.jenkins.io/publish-over-ssh/)로 파일을 올리지 않고, 서버에 이미 있는 `docker-compose.yml` 디렉터리에서 `docker compose pull && docker compose up -d`만 실행합니다.
|
||||
|
||||
| Secret | 설명 |
|
||||
|--------|------|
|
||||
| `REGISTRY_USERNAME` | push 전용 계정 |
|
||||
| `REGISTRY_PASSWORD` | push 전용 토큰 (write) |
|
||||
### Jenkins Credentials
|
||||
|
||||
레지스트리 호스트는 `docker.sejong.dev`로 고정입니다. PR job은 이 시크릿을 쓰지 않습니다. 러너에 상시 `docker login` 하지 마세요.
|
||||
Username/password 자격 증명 ID: `docker-sejong-dev`
|
||||
|
||||
| 필드 | 설명 |
|
||||
|------|------|
|
||||
| Username | push 전용 계정 |
|
||||
| Password | push 전용 토큰 (write) |
|
||||
|
||||
레지스트리 호스트는 `docker.sejong.dev`로 고정입니다. PR 빌드는 이 자격 증명을 쓰지 않습니다. 에이전트에 상시 `docker login` 하지 마세요.
|
||||
|
||||
Multibranch Pipeline(또는 Gitea/Git 브랜치 소스)로 이 레포를 연결하면 PR과 `main`이 각각 잡으로 뜹니다.
|
||||
|
||||
### Publish Over SSH
|
||||
|
||||
1. Jenkins에 **Publish Over SSH** 플러그인 설치
|
||||
2. **Manage Jenkins → System → Publish over SSH** 에 배포 서버 등록 (호스트, 사용자, 키 또는 비밀번호)
|
||||
3. 등록한 **Name**이 Jenkinsfile의 `SSH_CONFIG_NAME`과 같아야 함 (`sejong-office`)
|
||||
4. 배포 명령은 SSH 접속 후 `sudo -n su -c '...'` 로 root에서 실행함. 대화형 `sudo su`와 비밀번호 입력은 CI에서 불가
|
||||
5. Publish Over SSH에 넣은 **그 계정**으로 서버에 로그인한 뒤, 한 번만 NOPASSWD를 설정:
|
||||
|
||||
```bash
|
||||
sudo visudo -f /etc/sudoers.d/sejong-office-deploy
|
||||
```
|
||||
|
||||
파일 내용 (SSH 사용자 이름으로 바꿀 것):
|
||||
|
||||
```
|
||||
youruser ALL=(ALL) NOPASSWD: /bin/su, /usr/bin/su
|
||||
```
|
||||
|
||||
저장 후 비밀번호 없이 root가 되는지 확인:
|
||||
|
||||
```bash
|
||||
sudo -n su -c 'whoami'
|
||||
# root
|
||||
```
|
||||
|
||||
`sudo -n`은 비밀번호를 묻지 않고, 설정이 안 되어 있으면 바로 실패합니다. Jenkinsfile에 비밀번호를 넣지 마세요.
|
||||
|
||||
서버 compose 경로는 Jenkinsfile `DEPLOY_DIR` (`/volume1/docker/sejong-office`)입니다. 이름이나 경로가 다르면 Jenkinsfile을 수정하면 됩니다.
|
||||
|
||||
### 서버 배포 체크리스트
|
||||
|
||||
1. **pull 전용** 토큰으로 서버에서 `docker login docker.sejong.dev` (CI push 토큰과 분리)
|
||||
1. 배포 디렉터리에 `docker-compose.yml`과 `.env`를 두고, **pull 전용** 토큰으로 `docker login docker.sejong.dev` (CI push 토큰과 분리)
|
||||
2. `.env`에 `JWT_SECRET`, URL 설정
|
||||
3. 이미지 갱신 후: `docker compose pull && docker compose up -d`
|
||||
4. `JWT_SECRET`은 Git·이미지·Actions Secret에 넣지 않음 (서버 `.env`만)
|
||||
3. `main` 배포 잡이 이미지 push 후 그 디렉터리에서 compose pull/up 실행
|
||||
4. `JWT_SECRET`은 Git·이미지·Jenkins Credential에 넣지 않음 (서버 `.env`만)
|
||||
|
||||
## 로컬 개발
|
||||
|
||||
|
||||
Reference in new issue
Block a user