Skip to main content
Version: 1.0.1

Quick Start

Get Milvaion running locally and execute your first job in under 10 minutes.

Prerequisites

You need:

  • Docker Desktop (v20.10+) with Docker Compose
  • Web browser for the dashboard and built-in Open Api Rest Client
  • (Optional )curl or a REST client (Postman, Insomnia)

Verify Docker is installed:

docker --version
# Docker version 24.0.0 or higher

docker compose version
# Docker Compose version v2.20.0 or higher

Step 1: Start the Stack

Create a file named docker-compose.yml or download directly from GitHub:

curl -o docker-compose.yml https://raw.githubusercontent.com/Milvasoft/milvaion/master/build/docker-compose.yml
services:
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: milvaion-postgres
environment:
POSTGRES_DB: MilvaionDb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: N4SQp.qW>6?xwWzg
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- milvaion-network
restart: unless-stopped
mem_limit: 8096m
mem_reservation: 2048m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d MilvaionDb"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30s

# Redis Cache & Scheduler
redis:
image: redis:7-alpine
container_name: milvaion-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- milvaion-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5

# RabbitMQ Message Broker
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: milvaion-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672" # AMQP port
- "15672:15672" # Management UI
volumes:
- rabbitmq_data:/var/lib/rabbitmq
networks:
- milvaion-network
restart: unless-stopped
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 5

# Seq log server
seq:
image: datalust/seq:latest
container_name: milvaion-seq
environment:
- SEQ_FIRSTRUN_ADMINPASSWORD=ChangeMe123!
- ACCEPT_EULA=Y
ports:
- "5341:80" # Seq default UI port mapped to host 5341
volumes:
- seq_data:/data
networks:
- milvaion-network
restart: unless-stopped
labels:
- "com.milvaion.service=seq"
- "com.milvaion.description=Seq logging server"

# Milvaion API (Producer/Scheduler)
milvaion-api:
image: milvasoft/milvaion-api:latest
container_name: milvaion-api
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:5000
- Cors__Policies__AllowAll__AllowCredentials=false
- MILVAION_ROOT_PASSWORD=admin
- MILVA_ENV=prod
ports:
- "5000:5000" # API + React SPA (http://localhost:5000)
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
networks:
- milvaion-network
restart: unless-stopped
mem_limit: 2048m
labels:
- "com.milvaion.service=api"
- "com.milvaion.description=Milvaion API with embedded React UI"

# SampleWorker (Job Executor) - Scalable
milvaion-sample-worker:
image: milvasoft/milvaion-sample-worker:latest
environment:
- Worker__WorkerId=sample-worker
depends_on:
rabbitmq:
condition: service_healthy
redis:
condition: service_healthy
milvaion-api:
condition: service_started
networks:
- milvaion-network
restart: unless-stopped
mem_limit: 1024m
# Enable scaling: docker-compose up --scale sample-worker=4 -d
deploy:
replicas: 1

volumes:
postgres_data:
driver: local
redis_data:
driver: local
rabbitmq_data:
driver: local
seq_data:
driver: local

networks:
milvaion-network:
driver: bridge

Start all services:

docker compose up -d

Wait 30-60 seconds for all services to initialize, then verify:

docker compose ps

You should see all containers running:

CONTAINER ID   IMAGE                                     COMMAND                  CREATED         STATUS                   PORTS                                                                                                         NAMES
38cc1994f7b4 milvasoft/milvaion-sample-worker:latest "dotnet SampleWorker…" 4 minutes ago Up 4 minutes (healthy) milvaion-quick-milvaion-sample-worker-1
30161c96e5a6 milvasoft/milvaion-api:latest "dotnet Milvaion.Api…" 4 minutes ago Up 4 minutes (healthy) 0.0.0.0:5000->5000/tcp milvaion-api
2e7cea97cc80 rabbitmq:3-management-alpine "docker-entrypoint.s…" 4 minutes ago Up 4 minutes (healthy) 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp milvaion-rabbitmq
2a3b8aa49b48 redis:7-alpine "docker-entrypoint.s…" 4 minutes ago Up 4 minutes (healthy) 0.0.0.0:6379->6379/tcp milvaion-redis
ada407651b06 datalust/seq:latest "/bin/seqentry" 4 minutes ago Up 4 minutes 443/tcp, 5341/tcp, 45341/tcp, 0.0.0.0:5341->80/tcp milvaion-seq
9e24e3a3d5d8 postgres:16-alpine "docker-entrypoint.s…" 4 minutes ago Up 4 minutes (healthy) 0.0.0.0:5432->5432/tcp

Step 2: Access the Dashboard and Rest Client

Dashboard

Open your browser: http://localhost:5000

You'll see the Milvaion UI. For more information, see the detailed UI documentation.

Milvaion Dashboard

Default username is rootuser. System-wide access is permitted. If you didn't set MILVAION_ROOT_PASSWORD in docker-compose, to get the initial user password:

docker logs milvaion-api 2>&1 | grep -i "Initial root user password"
Note

You can change the password via /api/v1/account/password/change endpoint.

Rest Client

Open your browser: http://localhost:5000/api/documentation/index.html

You'll see the Milvaion rest client. A permanent token will be set up for you. Milvaion Dashboard

Without proxy, the api will serve only http. For http/https switch on rest client; Milvaion Dashboard

Step 3: Create Your First Job

Use the Dashboard UI, Rest Client or make an API call:

curl -X POST http://localhost:5000/api/v1/jobs/job \
-H "Content-Type: application/json" \
-H "Accept-Language: en-US" \
-d '{
"displayName": "My First Job",
"workerId": "sample-worker",
"selectedJobName": "SampleSendEmailJob",
"description": "This is a test job!",
"jobData": "{\n \"to\": \"[email protected]\",\n \"subject\": \"Test email subject\",\n \"body\": \"Test email body.\"\n}",
"isActive": true,
"concurrentExecutionPolicy": 0,
"tags": "test,first-job",
"zombieTimeoutMinutes": null,
"executionTimeoutSeconds": null,
"autoDisableSettings": {
"enabled": true,
"threshold": null
},
"cronExpression": "0 * * * * *"
}'

What this does:

FieldValueMeaning
displayName"My First Job"Human-readable name shown in dashboard
description"This is a test job!"Human-readable description shown in dashboard
tags"test,first-job"Comma seperated tags for job grouping.
workerId"sample-worker"Routes to the test worker. This must be exactly the same as your worker configuration.
selectedJobName"SampleSendEmailJob"Job class name in worker. Must match a job class in the worker
cronExpression0 * * * * *Schedule configuration. Run every minute
executeAt"2026-01-15T15:39:00.000Z"One time job schedule time. Send null for the recurring jobs. If both cronExpression and executeAt sent, cronExpression have priority.
concurrentExecutionPolicy0 -> Skip , 1 -> QueueScheduling mechanism when concurrent execution happens.
isActivetrueJob is active or not. Deactivated jobs will not create executions.
executionTimeoutSecondsnullJob-specific execution timeout in seconds. If set, worker will cancel the job after this duration and mark it as TimedOut. If null, worker's JobConsumerConfig.ExecutionTimeoutSeconds is used as fallback. Default: null (use worker config, typically 3600 seconds = 1 hour).
zombieTimeoutMinutesnullJob-specific zombie timeout in minutes. If set, occurrences stuck in Queued status longer than this will be marked as Failed. If null, global ZombieDetector timeout (10 minutes) is used. Useful for long-running jobs that need higher timeout thresholds..
jobDataJSON objectData passed to the job
autoDisableSettingsJSON objectAuto disable settings for consecutive failing.

You should receive:

{
"isSuccess": true,
"statusCode": 200,
"messages": [
{
"key": "",
"message": "Operation successful!",
"type": 1
}
],
"data": "019c1b4b-6f4a-75fb-b094-0dec83f168f5",
"metadatas": []
}

The data field is your new Job ID.

Step 4: Watch It Run

  1. Go to http://localhost:5000
  2. Login with your credentials.
  3. Click Jobs in the sidebar
  4. Click your job name
  5. Wait up to 1 minute for the cron trigger
  6. See the Execution History populate with occurrences in real-time
  7. Click an occurrence to see real-time logs

Option B: Worker Logs

docker logs -f milvaion-sample-worker

Expected output when job runs:

info: SampleJob[0]
🚀 SampleJob started!
info: SampleJob[0]
Job ID: f47ac10b-58cc-4372-a567-0e02b2c3d479
info: SampleJob[0]
Received data: {"message":"Hello from Milvaion!"}
info: SampleJob[0]
⏳ Processing step 1/5...
info: SampleJob[0]
⏳ Processing step 2/5...
...
info: SampleJob[0]
✅ SampleJob completed successfully!

Step 5: Trigger Manually (Optional)

Don't want to wait for cron → Trigger immediately via Dashboard or:

curl -X POST "http://localhost:5000/api/v1/jobs/job/trigger" \
-H "Content-Type: application/json" \
-d '{
"jobId": "YOUR_JOB_ID",
"reason": "Manual trigger by user"
"force": true
}'

Verify the System

API Health

curl http://localhost:5000/api/v1/healthcheck

RabbitMQ Management

Open http://localhost:15672 (guest/guest) to see queues and message flow.

Cleanup

Stop all services:

docker compose down

Stop and delete all data (fresh start):

docker compose down -v

Troubleshooting

"Job not executing"

  1. Check worker is running: docker compose ps
  2. Check worker logs: docker logs milvaion-sample-worker
  3. Verify job is active in dashboard
  4. Check RabbitMQ has queues: http://localhost:15672 → Queues

"API won't start"

  1. Check PostgreSQL is healthy: docker logs milvaion-postgres
  2. Wait for migrations: docker logs milvaion-api | grep -i migration
  3. Port conflict → Change 5000:8080 to 5001:8080

"Worker can't connect"

  1. Ensure worker is on same Docker network
  2. Use container names (rabbitmq, redis) in connection strings, not localhost
  3. Check RabbitMQ is healthy: docker logs milvaion-rabbitmq

What's Next?

Now that Milvaion is running:

  1. Core Concepts - Understand the architecture
  2. Your First Worker - Create a custom worker with your own jobs
  3. Configuration Reference - All available settings