DEV Community

Cover image for Deploying ClearML as an Azure ML Alternative
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya Originally published at docs.vultr.com

Deploying ClearML as an Azure ML Alternative

Azure Machine Learning ties experiment tracking, pipelines, and model serving to Azure-specific APIs and managed-compute pricing. ClearML is an open-source MLOps platform that provides the same capabilities — self-hosted, on any infrastructure. This guide deploys ClearML Server with Docker Compose and Traefik, configures agents for remote execution, tracks an experiment, builds a pipeline, runs hyperparameter optimization, and serves a model with Triton.

Prerequisites: a Linux server, non-root sudo user, Docker + Docker Compose, DNS A records for app.clearml.example.com, api.clearml.example.com, files.clearml.example.com. GPU workloads (optional) need the NVIDIA Container Toolkit on the agent host.


Architecture

Azure ML ClearML equivalent
Azure ML Studio ClearML Web UI
Azure ML Experiments Experiment Manager (auto-tracking)
Azure ML Jobs Agent + Tasks
Azure ML Pipelines ClearML Pipelines (Python DAG)
Azure ML Model Registry Model Repository
Azure ML Endpoints ClearML Serving (Triton)

Server components: API server, web UI, file server — backed by MongoDB + Elasticsearch. Agents are worker daemons that pull tasks from queues and run them on any machine with Python.


Deploy ClearML Server

$ echo "vm.max_map_count=524288" | sudo tee /etc/sysctl.d/99-clearml.conf
$ sudo sysctl --system
$ sudo systemctl restart docker
$ sudo mkdir -p /opt/clearml/{data/elastic_7,data/mongo_4/db,data/mongo_4/configdb,data/redis,data/fileserver,logs,config}
$ sudo chown -R 1000:1000 /opt/clearml
$ mkdir -p ~/clearml && cd ~/clearml
$ curl -fsSL https://raw.githubusercontent.com/clearml/clearml-server/master/docker/docker-compose.yml -o docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Edit docker-compose.yml: comment out every ports: block under apiserver, webserver, fileserver (Traefik handles routing), and set named bridge networks:

networks:
  backend:
    name: clearml_backend
    driver: bridge
  frontend:
    name: clearml_frontend
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Create .env (replace clearml.example.com with your domain):

CLEARML_WEB_HOST=https://app.clearml.example.com
CLEARML_API_HOST=https://api.clearml.example.com
CLEARML_FILES_HOST=https://files.clearml.example.com
Enter fullscreen mode Exit fullscreen mode
$ docker compose up -d
$ docker compose ps
$ docker compose logs --tail 50
Enter fullscreen mode Exit fullscreen mode

Configure Traefik

$ mkdir -p ~/clearml/traefik && cd ~/clearml/traefik
$ mkdir -p letsencrypt && touch letsencrypt/acme.json
$ chmod 600 letsencrypt/acme.json
Enter fullscreen mode Exit fullscreen mode

.env (replace with your email):

LETSENCRYPT_EMAIL=admin@example.com
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml:

services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--log.level=INFO"
      - "--providers.file.filename=/etc/traefik/dynamic_conf.yml"
      - "--entryPoints.web.address=:80"
      - "--entryPoints.websecure.address=:443"
      - "--entryPoints.web.http.redirections.entrypoint.to=websecure"
      - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=web"
      - "--certificatesResolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesResolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "./dynamic_conf.yml:/etc/traefik/dynamic_conf.yml:ro"
    networks:
      - clearml-frontend
    restart: unless-stopped

networks:
  clearml-frontend:
    name: clearml_frontend
    external: true
Enter fullscreen mode Exit fullscreen mode

dynamic_conf.yml routes each subdomain to its container (clearml-webserver:80, clearml-apiserver:8008, clearml-fileserver:8081) with certResolver: le. Full rules in the source repo.

$ docker compose up -d
$ docker logs traefik 2>&1 | grep -i certificate
Enter fullscreen mode Exit fullscreen mode

Configure ClearML Server

  1. Visit https://app.clearml.example.com, create the admin account (username + company name).
  2. Settings → Workspace → Create new credentials.
  3. Save the credentials block — needed for agents and the SDK:
api {
  web_server: https://app.clearml.example.com
  api_server: https://api.clearml.example.com
  files_server: https://files.clearml.example.com
  credentials {
    "access_key" = "YOUR_ACCESS_KEY"
    "secret_key" = "YOUR_SECRET_KEY"
  }
}
Enter fullscreen mode Exit fullscreen mode

Deploy an Agent

Agents can run on the server itself or a dedicated (ideally GPU-enabled) machine.

$ mkdir -p ~/clearml-agent && cd ~/clearml-agent
$ sudo apt install python3.12-venv -y
$ python3 -m venv clearml_venv
$ source clearml_venv/bin/activate
$ pip install clearml-agent
$ clearml-agent init
Enter fullscreen mode Exit fullscreen mode

Paste the credentials block when prompted, accept defaults for the rest. Then start it:

$ clearml-agent daemon --queue default --detached
Enter fullscreen mode Exit fullscreen mode

GPU workloads:

$ clearml-agent daemon --gpus 0,1 --queue default --detached
Enter fullscreen mode Exit fullscreen mode

Confirm it registered under Workers & Queues → Workers in the web UI.


Install the SDK

$ source ~/clearml-agent/clearml_venv/bin/activate
$ pip install clearml scikit-learn joblib pandas
$ clearml-init
Enter fullscreen mode Exit fullscreen mode

Paste the credentials block again when prompted — saves to ~/clearml.conf.


Track an Experiment

$ mkdir -p ~/clearml/experiments && cd ~/clearml/experiments
$ nano 01_first_experiment.py
Enter fullscreen mode Exit fullscreen mode
import joblib
from clearml import Task
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

task = Task.init(project_name='ClearML Tutorial', task_name='01_First_Experiment', tags=['tutorial'])

hyperparams = {'n_estimators': 100, 'max_depth': 5, 'random_state': 42}
task.connect(hyperparams)

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=42)

clf = RandomForestClassifier(**hyperparams)
clf.fit(X_train, y_train)

accuracy = accuracy_score(y_test, clf.predict(X_test))
task.get_logger().report_scalar(title='Performance', series='Accuracy', value=accuracy, iteration=1)

joblib.dump(clf, 'iris_rf_model.pkl')
task.upload_artifact(name='trained_model', artifact_object='iris_rf_model.pkl')
task.close()
Enter fullscreen mode Exit fullscreen mode
$ python3 01_first_experiment.py
Enter fullscreen mode Exit fullscreen mode

Task.init auto-captures code, environment, and hyperparameters — no manual logging needed beyond report_scalar. Open the printed task URL to see it in the web UI: Execution, Configuration, Artifacts, Console, Scalars, Plots tabs.


Build a Pipeline

clearml.PipelineController chains functions into a DAG; step outputs feed downstream steps automatically:

from clearml import PipelineController

def step_one(pickle_data_url):
    import pickle, pandas as pd
    from clearml import StorageManager
    local_pkl = StorageManager.get_local_copy(remote_url=pickle_data_url)
    with open(local_pkl, 'rb') as f:
        iris = pickle.load(f)
    df = pd.DataFrame(iris['data'], columns=iris['feature_names'])
    df['target'] = iris['target']
    return df

def step_two(data_frame, test_size=0.2, random_state=42):
    from sklearn.model_selection import train_test_split
    y = data_frame['target']
    X = data_frame.drop(columns=['target'])
    return train_test_split(X, y, test_size=test_size, random_state=random_state)

def step_three(data):
    from sklearn.linear_model import LogisticRegression
    X_train, X_test, y_train, y_test = data
    model = LogisticRegression(solver='lbfgs', max_iter=1000)
    model.fit(X_train, y_train)
    return model

if __name__ == '__main__':
    pipe = PipelineController(project='ClearML Tutorial', name='02_Pipeline_Experiment', version='1.0', add_pipeline_tags=True)
    pipe.add_parameter(name='url', default='https://github.com/allegroai/events/raw/master/odsc20-east/generic/iris_dataset.pkl')
    pipe.add_function_step(name='step_one', function=step_one, function_kwargs=dict(pickle_data_url='${pipeline.url}'), function_return=['data_frame'], cache_executed_step=True)
    pipe.add_function_step(name='step_two', function=step_two, function_kwargs=dict(data_frame='${step_one.data_frame}'), function_return=['processed_data'], cache_executed_step=True)
    pipe.add_function_step(name='step_three', function=step_three, function_kwargs=dict(data='${step_two.processed_data}'), function_return=['model'], cache_executed_step=True)
    pipe.start_locally(run_pipeline_steps_locally=True)
Enter fullscreen mode Exit fullscreen mode
$ python3 02_pipeline.py
Enter fullscreen mode Exit fullscreen mode

View the execution graph under the project in the web UI.


Run Hyperparameter Optimization

ClearML clones a completed base task and spawns trials across a defined search space:

from clearml import Task
from clearml.automation import HyperParameterOptimizer, DiscreteParameterRange, UniformIntegerParameterRange, RandomSearch

tasks = Task.get_tasks(project_name='ClearML Tutorial', task_filter={'status': ['completed', 'published']}, task_name='01_First_Experiment')
base_task_id = tasks[-1].id

Task.init(project_name='ClearML Tutorial', task_name='03_Hyperparameter_Optimization', task_type=Task.TaskTypes.optimizer)

optimizer = HyperParameterOptimizer(
    base_task_id=base_task_id,
    hyper_parameters=[
        UniformIntegerParameterRange('General/n_estimators', min_value=10, max_value=200, step_size=20),
        DiscreteParameterRange('General/max_depth', values=[3, 5, 7, 10])
    ],
    objective_metric_title='Performance',
    objective_metric_series='Accuracy',
    objective_metric_sign='max',
    optimizer_class=RandomSearch,
    max_number_of_concurrent_tasks=2,
    total_max_jobs=6
)
optimizer.start()
optimizer.wait()
top_exp = optimizer.get_top_experiments(1)
Enter fullscreen mode Exit fullscreen mode
$ python3 03_hpo.py
Enter fullscreen mode Exit fullscreen mode

Run the base experiment first — HPO needs a completed task to clone.


Serve Models with Triton

$ cd ~/clearml
$ git clone https://github.com/clearml/clearml-serving.git
$ pip install clearml-serving
$ clearml-serving create --name "serving-example"
Enter fullscreen mode Exit fullscreen mode

Copy the printed Serving Service ID, then edit clearml-serving/docker/.env:

CLEARML_WEB_HOST="https://app.clearml.example.com"
CLEARML_API_HOST="https://api.clearml.example.com"
CLEARML_FILES_HOST="https://files.clearml.example.com"
CLEARML_API_ACCESS_KEY="YOUR_ACCESS_KEY"
CLEARML_API_SECRET_KEY="YOUR_SECRET_KEY"
CLEARML_SERVING_TASK_ID="SERVING_SERVICE_ID"
Enter fullscreen mode Exit fullscreen mode
$ cd ~/clearml/clearml-serving/docker
$ docker compose --env-file .env -f docker-compose-triton.yml up -d
$ pip install -r ~/clearml/clearml-serving/examples/pytorch/requirements.txt
$ python3 ~/clearml/clearml-serving/examples/pytorch/train_pytorch_mnist.py
Enter fullscreen mode Exit fullscreen mode

Grab the Model ID from the task's Artifacts tab, then register the endpoint:

$ clearml-serving --id SERVING_SERVICE_ID model add \
    --engine triton \
    --endpoint "test_model_pytorch" \
    --preprocess "clearml-serving/examples/pytorch/preprocess.py" \
    --model-id MODEL_ID \
    --input-size 1 28 28 \
    --input-name "INPUT__0" \
    --input-type float32 \
    --output-size 10 \
    --output-name "OUTPUT__0" \
    --output-type float32
$ docker compose --env-file .env -f docker-compose-triton.yml restart
Enter fullscreen mode Exit fullscreen mode

Test it (replace SERVER-IP):

$ curl -X POST "http://SERVER-IP:8080/serve/test_model_pytorch" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://raw.githubusercontent.com/clearml/clearml-serving/main/examples/pytorch/5.jpg"}'
Enter fullscreen mode Exit fullscreen mode

Verify

$ curl -s https://api.clearml.example.com/debug.ping | head -c 100
$ curl -s -o /dev/null -w "%{http_code}" https://files.clearml.example.com/
Enter fullscreen mode Exit fullscreen mode

Confirm the agent shows under Workers & Queues, the first experiment has metrics/artifacts, and cloning + enqueuing a modified experiment gets picked up by the agent.


Migrating from Azure ML

  • Experiments: azure.ai.ml job definitions → clearml.Task (auto-captures Git state, env, uncommitted changes).
  • Training jobs: managed compute clusters → agents on any hardware; task.execute_remotely() or enqueue via UI.
  • Pipelines: DAGs via azure.ai.mlPipelineController or @pipeline decorator.
  • HPO: HyperDrive → HyperParameterOptimizer, running on your own agents.
  • Model registry: azure.ai.ml registration → OutputModel, with full lineage.
  • Endpoints: Real-time/Batch Endpoints → ClearML Serving + Triton, any infrastructure.
  • Auth: Entra ID → API keys via clearml.conf or CLEARML_API_ACCESS_KEY/CLEARML_API_SECRET_KEY.
  • Compute targets → queues: name a compute target at submission vs. deploy agents and submit to a matching queue.
  • MLflow: existing mlflow.log_* calls route through ClearML's MLflow-compatible backend without a rewrite.

Next Steps

ClearML Server is running behind Traefik with an agent, tracked experiments, a pipeline, HPO, and a served model. From here:

  • Add more agents on GPU hosts and split queues by workload type
  • Wire pipeline runs into CI so training kicks off on every merge
  • Move the Elasticsearch/MongoDB volumes to backed-up, monitored storage before relying on this for production models

For the full guide, visit the original article on Vultr Docs.

Top comments (0)