{"slug": "deploying-clearml-as-an-azure-ml-alternative", "title": "Deploying ClearML as an Azure ML Alternative", "summary": "ClearML, an open-source MLOps platform, can be deployed as a self-hosted alternative to Azure Machine Learning, offering experiment tracking, pipelines, and model serving on any infrastructure. A developer's guide details setting up ClearML Server with Docker Compose and Traefik, configuring agents for remote execution, and integrating Triton for model serving, providing a cost-effective and portable solution for MLOps workflows.", "body_md": "Azure Machine Learning ties experiment tracking, pipelines, and model serving to Azure-specific APIs and managed-compute pricing. [ClearML](https://clear.ml/) 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.\n\nPrerequisites:a Linux server, non-root sudo user, Docker + Docker Compose, DNS A records for`app.clearml.example.com`\n\n,`api.clearml.example.com`\n\n,`files.clearml.example.com`\n\n. GPU workloads (optional) need the NVIDIA Container Toolkit on the agent host.\n\n| Azure ML | ClearML equivalent |\n|---|---|\n| Azure ML Studio | ClearML Web UI |\n| Azure ML Experiments | Experiment Manager (auto-tracking) |\n| Azure ML Jobs | Agent + Tasks |\n| Azure ML Pipelines | ClearML Pipelines (Python DAG) |\n| Azure ML Model Registry | Model Repository |\n| Azure ML Endpoints | ClearML Serving (Triton) |\n\nServer 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.\n\n``` bash\n$ echo \"vm.max_map_count=524288\" | sudo tee /etc/sysctl.d/99-clearml.conf\n$ sudo sysctl --system\n$ sudo systemctl restart docker\n$ sudo mkdir -p /opt/clearml/{data/elastic_7,data/mongo_4/db,data/mongo_4/configdb,data/redis,data/fileserver,logs,config}\n$ sudo chown -R 1000:1000 /opt/clearml\n$ mkdir -p ~/clearml && cd ~/clearml\n$ curl -fsSL https://raw.githubusercontent.com/clearml/clearml-server/master/docker/docker-compose.yml -o docker-compose.yml\n```\n\nEdit `docker-compose.yml`\n\n: comment out every `ports:`\n\nblock under `apiserver`\n\n, `webserver`\n\n, `fileserver`\n\n(Traefik handles routing), and set named bridge networks:\n\n```\nnetworks:\n  backend:\n    name: clearml_backend\n    driver: bridge\n  frontend:\n    name: clearml_frontend\n    driver: bridge\n```\n\nCreate `.env`\n\n(replace `clearml.example.com`\n\nwith your domain):\n\n```\nCLEARML_WEB_HOST=https://app.clearml.example.com\nCLEARML_API_HOST=https://api.clearml.example.com\nCLEARML_FILES_HOST=https://files.clearml.example.com\nbash\n$ docker compose up -d\n$ docker compose ps\n$ docker compose logs --tail 50\nbash\n$ mkdir -p ~/clearml/traefik && cd ~/clearml/traefik\n$ mkdir -p letsencrypt && touch letsencrypt/acme.json\n$ chmod 600 letsencrypt/acme.json\n```\n\n`.env`\n\n(replace with your email):\n\n```\nLETSENCRYPT_EMAIL=admin@example.com\n```\n\n`docker-compose.yml`\n\n:\n\n```\nservices:\n  traefik:\n    image: traefik:v3.6\n    container_name: traefik\n    command:\n      - \"--log.level=INFO\"\n      - \"--providers.file.filename=/etc/traefik/dynamic_conf.yml\"\n      - \"--entryPoints.web.address=:80\"\n      - \"--entryPoints.websecure.address=:443\"\n      - \"--entryPoints.web.http.redirections.entrypoint.to=websecure\"\n      - \"--certificatesResolvers.le.acme.httpChallenge.entryPoint=web\"\n      - \"--certificatesResolvers.le.acme.email=${LETSENCRYPT_EMAIL}\"\n      - \"--certificatesResolvers.le.acme.storage=/letsencrypt/acme.json\"\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - \"./letsencrypt:/letsencrypt\"\n      - \"./dynamic_conf.yml:/etc/traefik/dynamic_conf.yml:ro\"\n    networks:\n      - clearml-frontend\n    restart: unless-stopped\n\nnetworks:\n  clearml-frontend:\n    name: clearml_frontend\n    external: true\n```\n\n`dynamic_conf.yml`\n\nroutes each subdomain to its container (`clearml-webserver:80`\n\n, `clearml-apiserver:8008`\n\n, `clearml-fileserver:8081`\n\n) with `certResolver: le`\n\n. Full rules in the [source repo](https://github.com/clearml/clearml-server).\n\n``` bash\n$ docker compose up -d\n$ docker logs traefik 2>&1 | grep -i certificate\n```\n\n`https://app.clearml.example.com`\n\n, create the admin account (username + company name).\n\n```\napi {\n  web_server: https://app.clearml.example.com\n  api_server: https://api.clearml.example.com\n  files_server: https://files.clearml.example.com\n  credentials {\n    \"access_key\" = \"YOUR_ACCESS_KEY\"\n    \"secret_key\" = \"YOUR_SECRET_KEY\"\n  }\n}\n```\n\nAgents can run on the server itself or a dedicated (ideally GPU-enabled) machine.\n\n``` bash\n$ mkdir -p ~/clearml-agent && cd ~/clearml-agent\n$ sudo apt install python3.12-venv -y\n$ python3 -m venv clearml_venv\n$ source clearml_venv/bin/activate\n$ pip install clearml-agent\n$ clearml-agent init\n```\n\nPaste the credentials block when prompted, accept defaults for the rest. Then start it:\n\n``` bash\n$ clearml-agent daemon --queue default --detached\n```\n\nGPU workloads:\n\n``` bash\n$ clearml-agent daemon --gpus 0,1 --queue default --detached\n```\n\nConfirm it registered under **Workers & Queues → Workers** in the web UI.\n\n``` bash\n$ source ~/clearml-agent/clearml_venv/bin/activate\n$ pip install clearml scikit-learn joblib pandas\n$ clearml-init\n```\n\nPaste the credentials block again when prompted — saves to `~/clearml.conf`\n\n.\n\n``` bash\n$ mkdir -p ~/clearml/experiments && cd ~/clearml/experiments\n$ nano 01_first_experiment.py\npython\nimport joblib\nfrom clearml import Task\nfrom sklearn.datasets import load_iris\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import accuracy_score\n\ntask = Task.init(project_name='ClearML Tutorial', task_name='01_First_Experiment', tags=['tutorial'])\n\nhyperparams = {'n_estimators': 100, 'max_depth': 5, 'random_state': 42}\ntask.connect(hyperparams)\n\niris = load_iris()\nX_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=42)\n\nclf = RandomForestClassifier(**hyperparams)\nclf.fit(X_train, y_train)\n\naccuracy = accuracy_score(y_test, clf.predict(X_test))\ntask.get_logger().report_scalar(title='Performance', series='Accuracy', value=accuracy, iteration=1)\n\njoblib.dump(clf, 'iris_rf_model.pkl')\ntask.upload_artifact(name='trained_model', artifact_object='iris_rf_model.pkl')\ntask.close()\nbash\n$ python3 01_first_experiment.py\n```\n\n`Task.init`\n\nauto-captures code, environment, and hyperparameters — no manual logging needed beyond `report_scalar`\n\n. Open the printed task URL to see it in the web UI: **Execution**, **Configuration**, **Artifacts**, **Console**, **Scalars**, **Plots** tabs.\n\n`clearml.PipelineController`\n\nchains functions into a DAG; step outputs feed downstream steps automatically:\n\n``` python\nfrom clearml import PipelineController\n\ndef step_one(pickle_data_url):\n    import pickle, pandas as pd\n    from clearml import StorageManager\n    local_pkl = StorageManager.get_local_copy(remote_url=pickle_data_url)\n    with open(local_pkl, 'rb') as f:\n        iris = pickle.load(f)\n    df = pd.DataFrame(iris['data'], columns=iris['feature_names'])\n    df['target'] = iris['target']\n    return df\n\ndef step_two(data_frame, test_size=0.2, random_state=42):\n    from sklearn.model_selection import train_test_split\n    y = data_frame['target']\n    X = data_frame.drop(columns=['target'])\n    return train_test_split(X, y, test_size=test_size, random_state=random_state)\n\ndef step_three(data):\n    from sklearn.linear_model import LogisticRegression\n    X_train, X_test, y_train, y_test = data\n    model = LogisticRegression(solver='lbfgs', max_iter=1000)\n    model.fit(X_train, y_train)\n    return model\n\nif __name__ == '__main__':\n    pipe = PipelineController(project='ClearML Tutorial', name='02_Pipeline_Experiment', version='1.0', add_pipeline_tags=True)\n    pipe.add_parameter(name='url', default='https://github.com/allegroai/events/raw/master/odsc20-east/generic/iris_dataset.pkl')\n    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)\n    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)\n    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)\n    pipe.start_locally(run_pipeline_steps_locally=True)\nbash\n$ python3 02_pipeline.py\n```\n\nView the execution graph under the project in the web UI.\n\nClearML clones a completed base task and spawns trials across a defined search space:\n\n``` python\nfrom clearml import Task\nfrom clearml.automation import HyperParameterOptimizer, DiscreteParameterRange, UniformIntegerParameterRange, RandomSearch\n\ntasks = Task.get_tasks(project_name='ClearML Tutorial', task_filter={'status': ['completed', 'published']}, task_name='01_First_Experiment')\nbase_task_id = tasks[-1].id\n\nTask.init(project_name='ClearML Tutorial', task_name='03_Hyperparameter_Optimization', task_type=Task.TaskTypes.optimizer)\n\noptimizer = HyperParameterOptimizer(\n    base_task_id=base_task_id,\n    hyper_parameters=[\n        UniformIntegerParameterRange('General/n_estimators', min_value=10, max_value=200, step_size=20),\n        DiscreteParameterRange('General/max_depth', values=[3, 5, 7, 10])\n    ],\n    objective_metric_title='Performance',\n    objective_metric_series='Accuracy',\n    objective_metric_sign='max',\n    optimizer_class=RandomSearch,\n    max_number_of_concurrent_tasks=2,\n    total_max_jobs=6\n)\noptimizer.start()\noptimizer.wait()\ntop_exp = optimizer.get_top_experiments(1)\nbash\n$ python3 03_hpo.py\n```\n\nRun the base experiment first — HPO needs a completed task to clone.\n\n``` bash\n$ cd ~/clearml\n$ git clone https://github.com/clearml/clearml-serving.git\n$ pip install clearml-serving\n$ clearml-serving create --name \"serving-example\"\n```\n\nCopy the printed Serving Service ID, then edit `clearml-serving/docker/.env`\n\n:\n\n```\nCLEARML_WEB_HOST=\"https://app.clearml.example.com\"\nCLEARML_API_HOST=\"https://api.clearml.example.com\"\nCLEARML_FILES_HOST=\"https://files.clearml.example.com\"\nCLEARML_API_ACCESS_KEY=\"YOUR_ACCESS_KEY\"\nCLEARML_API_SECRET_KEY=\"YOUR_SECRET_KEY\"\nCLEARML_SERVING_TASK_ID=\"SERVING_SERVICE_ID\"\nbash\n$ cd ~/clearml/clearml-serving/docker\n$ docker compose --env-file .env -f docker-compose-triton.yml up -d\n$ pip install -r ~/clearml/clearml-serving/examples/pytorch/requirements.txt\n$ python3 ~/clearml/clearml-serving/examples/pytorch/train_pytorch_mnist.py\n```\n\nGrab the Model ID from the task's **Artifacts** tab, then register the endpoint:\n\n``` bash\n$ clearml-serving --id SERVING_SERVICE_ID model add \\\n    --engine triton \\\n    --endpoint \"test_model_pytorch\" \\\n    --preprocess \"clearml-serving/examples/pytorch/preprocess.py\" \\\n    --model-id MODEL_ID \\\n    --input-size 1 28 28 \\\n    --input-name \"INPUT__0\" \\\n    --input-type float32 \\\n    --output-size 10 \\\n    --output-name \"OUTPUT__0\" \\\n    --output-type float32\n$ docker compose --env-file .env -f docker-compose-triton.yml restart\n```\n\nTest it (replace `SERVER-IP`\n\n):\n\n``` bash\n$ curl -X POST \"http://SERVER-IP:8080/serve/test_model_pytorch\" \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"url\": \"https://raw.githubusercontent.com/clearml/clearml-serving/main/examples/pytorch/5.jpg\"}'\nbash\n$ curl -s https://api.clearml.example.com/debug.ping | head -c 100\n$ curl -s -o /dev/null -w \"%{http_code}\" https://files.clearml.example.com/\n```\n\nConfirm the agent shows under **Workers & Queues**, the first experiment has metrics/artifacts, and cloning + enqueuing a modified experiment gets picked up by the agent.\n\n`azure.ai.ml`\n\njob definitions → `clearml.Task`\n\n(auto-captures Git state, env, uncommitted changes).`task.execute_remotely()`\n\nor enqueue via UI.`azure.ai.ml`\n\n→ `PipelineController`\n\nor `@pipeline`\n\ndecorator.`HyperParameterOptimizer`\n\n, running on your own agents.`azure.ai.ml`\n\nregistration → `OutputModel`\n\n, with full lineage.`clearml.conf`\n\nor `CLEARML_API_ACCESS_KEY`\n\n/`CLEARML_API_SECRET_KEY`\n\n.`mlflow.log_*`\n\ncalls route through ClearML's MLflow-compatible backend without a rewrite.ClearML Server is running behind Traefik with an agent, tracked experiments, a pipeline, HPO, and a served model. From here:\n\nFor the full guide, visit the original article on ** Vultr Docs**.", "url": "https://wpnews.pro/news/deploying-clearml-as-an-azure-ml-alternative", "canonical_source": "https://dev.to/vultr/deploying-clearml-as-an-azure-ml-alternative-3hoh", "published_at": "2026-08-26 17:37:59+00:00", "updated_at": "2026-08-26 17:44:26.211025+00:00", "lang": "en", "topics": ["mlops", "developer-tools"], "entities": ["ClearML", "Azure Machine Learning", "Docker Compose", "Traefik", "Triton"], "alternates": {"html": "https://wpnews.pro/news/deploying-clearml-as-an-azure-ml-alternative", "markdown": "https://wpnews.pro/news/deploying-clearml-as-an-azure-ml-alternative.md", "text": "https://wpnews.pro/news/deploying-clearml-as-an-azure-ml-alternative.txt", "jsonld": "https://wpnews.pro/news/deploying-clearml-as-an-azure-ml-alternative.jsonld"}}