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
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
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
$ docker compose up -d
$ docker compose ps
$ docker compose logs --tail 50
Configure Traefik
$ mkdir -p ~/clearml/traefik && cd ~/clearml/traefik
$ mkdir -p letsencrypt && touch letsencrypt/acme.json
$ chmod 600 letsencrypt/acme.json
.env (replace with your email):
LETSENCRYPT_EMAIL=admin@example.com
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
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
Configure ClearML Server
- Visit
https://app.clearml.example.com, create the admin account (username + company name). - Settings → Workspace → Create new credentials.
- 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"
}
}
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
Paste the credentials block when prompted, accept defaults for the rest. Then start it:
$ clearml-agent daemon --queue default --detached
GPU workloads:
$ clearml-agent daemon --gpus 0,1 --queue default --detached
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
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
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()
$ python3 01_first_experiment.py
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)
$ python3 02_pipeline.py
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)
$ python3 03_hpo.py
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"
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"
$ 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
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
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"}'
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/
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.mljob 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.ml→PipelineControlleror@pipelinedecorator. -
HPO: HyperDrive →
HyperParameterOptimizer, running on your own agents. -
Model registry:
azure.ai.mlregistration →OutputModel, with full lineage. - Endpoints: Real-time/Batch Endpoints → ClearML Serving + Triton, any infrastructure.
-
Auth: Entra ID → API keys via
clearml.conforCLEARML_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.
This article was originally published by DEV Community and written by Sanskriti Harmukh.
Read original article on DEV Community