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:
| Image | Dockerfile | Build Context | Build Args |
|---|---|---|---|
ampra-ui:latest | src/Ampra.UI/Dockerfile | src/Ampra.UI | VITE_API_URL=https://api.ampra.solar |
ampra-api:latest | src/Ampra.Web/Dockerfile | . (root) | — |
ampra-ml:latest | src/Ampra.ML/Dockerfile | src/Ampra.ML | — |
ampra-ingestion:latest | src/Ampra.MQTT/Dockerfile | . (root) | — |
ampra-docs:latest | src/Ampra.Docs/Dockerfile | src/Ampra.Docs | — |
Stage 5: Deploy Infrastructure
Infrastructure containers are deployed with persistent volumes:
| Container | Credentials Source | Volume |
|---|---|---|
| PostgreSQL | ampra-db-password | ampra-db-data |
| MongoDB | ampra-mongo-password | ampra-mongo-data |
| MinIO | ampra-minio-password | ampra-minio-data |
| Redis | — | ampra-redis-data |
| EMQX | ampra-emqx-password | ampra-emqx-data |
Stage 6: Deploy Application
Application containers are deployed with Traefik labels and environment variables injected from Jenkins credentials.
Jenkins Credentials
| Credential ID | Type | Used By |
|---|---|---|
ampra-ssh | SSH Key | Git checkout |
ampra-db-password | Secret | PostgreSQL password |
ampra-mongo-password | Secret | MongoDB password |
ampra-minio-password | Secret | MinIO password |
ampra-sendgrid-key | Secret | SendGrid email API key |
ampra-overseer-email | Secret | Overseer account email |
ampra-emqx-password | Secret | EMQX dashboard & super user password |
Environment Variables (Production)
API Container
| Variable | Description |
|---|---|
PORT | Server port (80) |
CORS_ALLOWED_ORIGINS | https://ampra.solar |
ConnectionStrings__DefaultConnection | PostgreSQL connection string |
ConnectionStrings__MongoDb | MongoDB connection string |
ConnectionStrings__Redis | Redis connection string |
MinIO__Endpoint | ampra-minio:9000 |
MinIO__PublicUrl | minio.ampra.solar |
MinIO__AccessKey | MinIO access key |
MinIO__SecretKey | MinIO secret key |
MinIO__BucketName | ampra-assets |
MinIO__UseSSL | false (internal) |
MinIO__PublicUseSSL | true (public URLs) |
Integrations__SendGridApiKey | SendGrid API key |
Auth__OverseerEmail | Overseer account email |
ML__ServiceUrl | http://ampra-ml:5050 |
MqttBroker__Host | ampra.solar |
MqttBroker__Port | 1883 |
MqttBroker__InternalHost | ampra-emqx |
MqttBroker__SuperUser | ampra-ingestion |
MqttBroker__SuperPassword | EMQX password |
ML Container
| Variable | Description |
|---|---|
MONGO_URL | MongoDB connection string |
MONGO_DB | Database name (ampradb) |
REDIS_URL | Redis URL |
MINIO_ENDPOINT | ampra-minio:9000 |
MINIO_ACCESS_KEY | MinIO access key |
MINIO_SECRET_KEY | MinIO secret key |
MINIO_BUCKET | ampra-assets |
MQTT Ingestion Container
| Variable | Description |
|---|---|
Mqtt__BrokerHost | ampra-emqx |
Mqtt__BrokerPort | 1883 |
Mqtt__ClientId | ampra-ingestion-worker |
Mqtt__Username | ampra-ingestion |
Mqtt__Password | EMQX password |
Mqtt__TopicFilter | ampra/sources/+/data |
ConnectionStrings__MongoDb | MongoDB connection string |
ConnectionStrings__PostgreSql | PostgreSQL connection string |
Ingestion__MongoDatabase | ampradb |
Ingestion__MongoCollection | normalized_sun_source_data |
Ingestion__ThrottleSeconds | 5 |
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.batfor 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
| Volume | Container | Mount Point | Purpose |
|---|---|---|---|
ampra-db-data | PostgreSQL | /var/lib/postgresql/data | All relational data |
ampra-mongo-data | MongoDB | /data/db | Telemetry documents |
ampra-minio-data | MinIO | /data | Uploaded images, ML models |
ampra-redis-data | Redis | /data | Cache persistence |
ampra-emqx-data | EMQX | /opt/emqx/data | Broker state |
These volumes persist across container restarts. Deleting volumes will result in data loss.