Skip to main content

CI/CD Pipeline

Ampra uses a Jenkins declarative pipeline for continuous integration and deployment. The pipeline builds, lints, and deploys the full stack to production.


Pipeline Architecture


Pipeline Agent

agent {
docker {
image 'docker:24.0-cli'
args '-v /var/run/docker.sock:/var/run/docker.sock -u root'
}
}

Runs inside a Docker-in-Docker agent with access to the host Docker socket for container management.


Pipeline Stages

Stage 1: Checkout

git branch: 'main', url: 'git@github.com:bytegrip/Ampra.git'

Clones the repository from GitHub using SSH credentials.

Stage 2: Lint UI

docker run --rm -v "$(pwd)/src/Ampra.UI":/app -w /app node:22-alpine \
sh -c "npm ci && npm run lint"

Runs ESLint on the React/TypeScript codebase using a disposable Node.js container.

Stage 3: Lint API

docker run --rm -v "$(pwd)":/src -w /src mcr.microsoft.com/dotnet/sdk:10.0 \
dotnet format Ampra.sln --verify-no-changes

Verifies C# code formatting using dotnet format in a disposable .NET SDK container.

Stage 4: Build Images

Five Docker images are built in sequence:

ImageDockerfileBuild ContextBuild Args
ampra-ui:latestsrc/Ampra.UI/Dockerfilesrc/Ampra.UIVITE_API_URL=https://api.ampra.solar
ampra-api:latestsrc/Ampra.Web/Dockerfile. (root)
ampra-ml:latestsrc/Ampra.ML/Dockerfilesrc/Ampra.ML
ampra-ingestion:latestsrc/Ampra.MQTT/Dockerfile. (root)
ampra-docs:latestsrc/Ampra.Docs/Dockerfilesrc/Ampra.Docs

Stage 5: Deploy Infrastructure

Infrastructure containers are deployed with persistent volumes:

ContainerCredentials SourceVolume
PostgreSQLampra-db-passwordampra-db-data
MongoDBampra-mongo-passwordampra-mongo-data
MinIOampra-minio-passwordampra-minio-data
Redisampra-redis-data
EMQXampra-emqx-passwordampra-emqx-data

Stage 6: Deploy Application

Application containers are deployed with Traefik labels and environment variables injected from Jenkins credentials.


Jenkins Credentials

Credential IDTypeUsed By
ampra-sshSSH KeyGit checkout
ampra-db-passwordSecretPostgreSQL password
ampra-mongo-passwordSecretMongoDB password
ampra-minio-passwordSecretMinIO password
ampra-sendgrid-keySecretSendGrid email API key
ampra-overseer-emailSecretOverseer account email
ampra-emqx-passwordSecretEMQX dashboard & super user password

Environment Variables (Production)

API Container

VariableDescription
PORTServer port (80)
CORS_ALLOWED_ORIGINShttps://ampra.solar
ConnectionStrings__DefaultConnectionPostgreSQL connection string
ConnectionStrings__MongoDbMongoDB connection string
ConnectionStrings__RedisRedis connection string
MinIO__Endpointampra-minio:9000
MinIO__PublicUrlminio.ampra.solar
MinIO__AccessKeyMinIO access key
MinIO__SecretKeyMinIO secret key
MinIO__BucketNameampra-assets
MinIO__UseSSLfalse (internal)
MinIO__PublicUseSSLtrue (public URLs)
Integrations__SendGridApiKeySendGrid API key
Auth__OverseerEmailOverseer account email
ML__ServiceUrlhttp://ampra-ml:5050
MqttBroker__Hostampra.solar
MqttBroker__Port1883
MqttBroker__InternalHostampra-emqx
MqttBroker__SuperUserampra-ingestion
MqttBroker__SuperPasswordEMQX password

ML Container

VariableDescription
MONGO_URLMongoDB connection string
MONGO_DBDatabase name (ampradb)
REDIS_URLRedis URL
MINIO_ENDPOINTampra-minio:9000
MINIO_ACCESS_KEYMinIO access key
MINIO_SECRET_KEYMinIO secret key
MINIO_BUCKETampra-assets

MQTT Ingestion Container

VariableDescription
Mqtt__BrokerHostampra-emqx
Mqtt__BrokerPort1883
Mqtt__ClientIdampra-ingestion-worker
Mqtt__Usernameampra-ingestion
Mqtt__PasswordEMQX password
Mqtt__TopicFilterampra/sources/+/data
ConnectionStrings__MongoDbMongoDB connection string
ConnectionStrings__PostgreSqlPostgreSQL connection string
Ingestion__MongoDatabaseampradb
Ingestion__MongoCollectionnormalized_sun_source_data
Ingestion__ThrottleSeconds5

Deployment Sequence

The Jenkins pipeline follows a strict deployment order to ensure dependencies are available:

Each container is force-removed before redeployment (docker rm -f), ensuring a clean state.


EMQX Configuration

Local Development (emqx.conf)

  • Auth/ACL callbacks point to host.docker.internal:5001
  • Used with run.bat for local testing

Production (emqx.prod.conf)

  • Auth/ACL callbacks point to ampra-api:80 (Docker network)
  • Mounted read-only into the EMQX container

Both configurations use HTTP-based authentication — EMQX delegates all client authentication and topic authorization to the Ampra API via the MqttAuthController.


Nginx Configuration

UI (src/Ampra.UI/nginx.conf)

server {
listen 80;
root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html; # SPA fallback
}

gzip on;
gzip_min_length 256;
}

Docs (src/Ampra.Docs/nginx.conf)

server {
listen 80;
root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

location ~* \.(css|js|svg|png|...)$ {
expires 1y;
add_header Cache-Control "public, immutable"; # Long-term asset caching
}
}

Persistent Volumes

VolumeContainerMount PointPurpose
ampra-db-dataPostgreSQL/var/lib/postgresql/dataAll relational data
ampra-mongo-dataMongoDB/data/dbTelemetry documents
ampra-minio-dataMinIO/dataUploaded images, ML models
ampra-redis-dataRedis/dataCache persistence
ampra-emqx-dataEMQX/opt/emqx/dataBroker state
warning

These volumes persist across container restarts. Deleting volumes will result in data loss.