feat: CI/CD workflow, Dockerfile, and K8s deployment
Some checks failed
CI / build (push) Failing after 20s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped

- Multi-stage Dockerfile (Maven + Node build, JRE runtime)
- Gitea Actions CI: build → docker → deploy
- K8s manifests: Deployment, Service (NodePort 30082), Ingress
- ServiceAccount + RBAC for kubectl access from pod
- Docker socket mount for image builds
- Ingress at deploy.cameleer.siegeln.net
- SPA config for serving frontend from Spring Boot
- cameleer-demo namespace for deployed apps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-03 09:44:49 +02:00
parent 5ed0d80695
commit 5bbec1e52a
4 changed files with 309 additions and 0 deletions

114
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,114 @@
name: CI
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
container:
image: gitea.siegeln.net/cameleer/cameleer-build:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Build UI
working-directory: ui
run: |
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
npm ci
npm run build
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Build Backend
run: mvn clean package -DskipTests -B
docker:
needs: build
runs-on: ubuntu-latest
container:
image: gitea.siegeln.net/cameleer/cameleer-docker-builder:1
credentials:
username: cameleer
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Login to registry
run: echo "$REGISTRY_TOKEN" | docker login gitea.siegeln.net -u cameleer --password-stdin
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Set up QEMU
run: docker run --rm --privileged gitea.siegeln.net/cameleer/binfmt:1 --install all
- name: Build and push
run: |
docker buildx create --use --name cibuilder
docker buildx build --platform linux/amd64 \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
-t gitea.siegeln.net/cameleer/cameleer-deploy-demo:${{ github.sha }} \
-t gitea.siegeln.net/cameleer/cameleer-deploy-demo:latest \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer-deploy-demo:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer-deploy-demo:buildcache,mode=max \
--provenance=false \
--push .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Cleanup
run: docker system prune -af --filter "until=24h"
if: always()
deploy:
needs: docker
runs-on: ubuntu-latest
container:
image: alpine/k8s:1.32.3
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Create deployer kubeconfig secret
run: |
kubectl create secret generic deployer-kubeconfig \
--namespace=cameleer \
--from-literal=config="$(echo "$KUBECONFIG_B64" | base64 -d)" \
--dry-run=client -o yaml | kubectl apply -f -
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Deploy
run: |
kubectl apply -f deploy/deploy-demo.yaml
kubectl -n cameleer set image deployment/cameleer-deploy-demo \
deploy-demo=gitea.siegeln.net/cameleer/cameleer-deploy-demo:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer-deploy-demo --timeout=120s
- name: Print URL
run: |
echo "===================================="
echo "Deploy Demo available at:"
echo "http://deploy.cameleer.siegeln.net"
echo "http://192.168.50.86:30082"
echo "===================================="

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM --platform=$BUILDPLATFORM maven:3.9-eclipse-temurin-21 AS backend-build
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B || true
COPY src/ src/
RUN mvn clean package -DskipTests -B
FROM --platform=$BUILDPLATFORM node:22-alpine AS ui-build
WORKDIR /ui
ARG REGISTRY_TOKEN
COPY ui/package.json ui/package-lock.json ui/.npmrc ./
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && \
npm ci
COPY ui/ .
RUN npm run build
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=backend-build /build/target/cameleer-deploy-demo-*.jar /app/server.jar
COPY --from=ui-build /ui/dist /app/static
EXPOSE 8082
ENV TZ=UTC
ENTRYPOINT exec java -Duser.timezone=UTC \
-Dserver.port=8082 \
-Dspring.web.resources.static-locations=file:/app/static/ \
-jar /app/server.jar

143
deploy/deploy-demo.yaml Normal file
View File

@@ -0,0 +1,143 @@
apiVersion: v1
kind: Namespace
metadata:
name: cameleer-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cameleer-deploy-demo
namespace: cameleer
labels:
app: cameleer-deploy-demo
spec:
replicas: 1
selector:
matchLabels:
app: cameleer-deploy-demo
template:
metadata:
labels:
app: cameleer-deploy-demo
spec:
imagePullSecrets:
- name: gitea-registry
serviceAccountName: cameleer-deployer
containers:
- name: deploy-demo
image: gitea.siegeln.net/cameleer/cameleer-deploy-demo:latest
ports:
- containerPort: 8082
env:
- name: CAMELEER_SERVER_URL
value: "http://cameleer3-server.cameleer.svc:8081"
- name: CAMELEER_BOOTSTRAP_TOKEN
valueFrom:
secretKeyRef:
name: cameleer-auth
key: CAMELEER_AUTH_TOKEN
- name: CAMELEER_REGISTRY
value: "gitea.siegeln.net/cameleer/demo-apps"
- name: CAMELEER_AGENT_MAVEN_URL
value: "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-agent/1.0-SNAPSHOT/cameleer3-agent-1.0-SNAPSHOT.jar"
- name: CAMELEER_DEMO_NAMESPACE
value: "cameleer-demo"
- name: CAMELEER_SERVER_UI
value: "http://192.168.50.86:30090"
- name: DOCKER_HOST
value: "unix:///var/run/docker.sock"
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: kubectl-config
mountPath: /root/.kube
readOnly: true
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/apps
port: 8082
initialDelaySeconds: 20
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/apps
port: 8082
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: kubectl-config
secret:
secretName: deployer-kubeconfig
---
apiVersion: v1
kind: Service
metadata:
name: cameleer-deploy-demo
namespace: cameleer
spec:
type: NodePort
selector:
app: cameleer-deploy-demo
ports:
- port: 8082
targetPort: 8082
nodePort: 30082
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: cameleer-deployer
namespace: cameleer
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cameleer-deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "services", "namespaces"]
verbs: ["get", "list", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cameleer-deployer
subjects:
- kind: ServiceAccount
name: cameleer-deployer
namespace: cameleer
roleRef:
kind: ClusterRole
name: cameleer-deployer
apiGroup: rbac.authorization.k8s.io
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cameleer-deploy-demo
namespace: cameleer
spec:
rules:
- host: deploy.cameleer.siegeln.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cameleer-deploy-demo
port:
number: 8082

View File

@@ -0,0 +1,25 @@
package com.cameleer.deploy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Serves the SPA frontend. All non-API, non-static paths forward to index.html
* so that client-side routing works.
*/
@Configuration
public class SpaConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// Forward non-API paths to index.html for SPA routing
registry.addViewController("/").setViewName("forward:/index.html");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "file:static/");
}
}