Add Jenkins CI and Portainer stack for image deployment
Jenkinsfile builds and pushes both the firebase (nginx/static) and sqlite (caddy+node) images to thinkserver:5000, guarded to main only. docker/portainer-stack.yml deploys the prebuilt sqlite image from the registry behind the external TLS-terminating nginx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vendored
+14
-3
@@ -49,13 +49,21 @@ pipeline {
|
|||||||
checkout scm
|
checkout scm
|
||||||
script {
|
script {
|
||||||
env.SHORT_SHA = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()
|
env.SHORT_SHA = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()
|
||||||
echo "Building tag ${env.SHORT_SHA} (build #${env.BUILD_NUMBER})"
|
// Only build/push on main. BRANCH_NAME is set by Multibranch jobs; it
|
||||||
|
// is unset in a plain "Pipeline from SCM" job, which already only ever
|
||||||
|
// checks out main, so treat unset as main too.
|
||||||
|
env.IS_MAIN = (env.BRANCH_NAME == null || env.BRANCH_NAME == 'main').toString()
|
||||||
|
if (env.IS_MAIN != 'true') {
|
||||||
|
echo "Branch '${env.BRANCH_NAME}' is not main — skipping image build/push."
|
||||||
|
} else {
|
||||||
|
echo "Building tag ${env.SHORT_SHA} (build #${env.BUILD_NUMBER})"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Registry login') {
|
stage('Registry login') {
|
||||||
when { expression { return params.REGISTRY_AUTH } }
|
when { allOf { expression { env.IS_MAIN == 'true' }; expression { return params.REGISTRY_AUTH } } }
|
||||||
steps {
|
steps {
|
||||||
withCredentials([usernamePassword(credentialsId: 'thinkserver-registry',
|
withCredentials([usernamePassword(credentialsId: 'thinkserver-registry',
|
||||||
usernameVariable: 'REG_USER',
|
usernameVariable: 'REG_USER',
|
||||||
@@ -66,6 +74,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Build firebase image') {
|
stage('Build firebase image') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
steps {
|
steps {
|
||||||
// Firebase config is compiled into the static bundle, so it must exist
|
// Firebase config is compiled into the static bundle, so it must exist
|
||||||
// at build time. Inject .env.local from the Jenkins secret file, then
|
// at build time. Inject .env.local from the Jenkins secret file, then
|
||||||
@@ -83,6 +92,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Build sqlite image') {
|
stage('Build sqlite image') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
steps {
|
steps {
|
||||||
// Server/SQLite build. BuildKit is required (syntax directive + cache
|
// Server/SQLite build. BuildKit is required (syntax directive + cache
|
||||||
// mount in docker/Dockerfile). Context is the repo root.
|
// mount in docker/Dockerfile). Context is the repo root.
|
||||||
@@ -97,7 +107,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Tag latest') {
|
stage('Tag latest') {
|
||||||
when { expression { return params.PUSH_LATEST } }
|
when { allOf { expression { env.IS_MAIN == 'true' }; expression { return params.PUSH_LATEST } } }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
docker tag "$FIREBASE_IMAGE:$SHORT_SHA" "$FIREBASE_IMAGE:latest"
|
docker tag "$FIREBASE_IMAGE:$SHORT_SHA" "$FIREBASE_IMAGE:latest"
|
||||||
@@ -107,6 +117,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Push') {
|
stage('Push') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
docker push "$FIREBASE_IMAGE:$SHORT_SHA"
|
docker push "$FIREBASE_IMAGE:$SHORT_SHA"
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# docker/portainer-stack.yml — Portainer stack for the SQLite/server build.
|
||||||
|
# Deploys the prebuilt image from the local registry (built + pushed by Jenkins).
|
||||||
|
# Paste into Portainer's web editor (Stacks -> Add stack -> Web editor).
|
||||||
|
#
|
||||||
|
# TLS is terminated by the external nginx, which should reverse_proxy to
|
||||||
|
# http://<docker-host>:8080 (and pass the WebSocket upgrade headers for /ws).
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: thinkserver:5000/ttrpg-tracker-sqlite:latest
|
||||||
|
container_name: ttrpg-tracker
|
||||||
|
ports:
|
||||||
|
- "8080:80" # host:container — nginx upstream is :8080
|
||||||
|
volumes:
|
||||||
|
- app-data:/data # SQLite DB lives here; back this up
|
||||||
|
environment:
|
||||||
|
- DB_PATH=/data/tracker.sqlite
|
||||||
|
- PORT=4001 # node backend port (internal; caddy proxies to it)
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-qO-", "http://localhost:80/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app-data:
|
||||||
Reference in New Issue
Block a user