Deploying ClearML as a GCP Vertex AI Alternative on Ubuntu A developer deployed ClearML, an open-source MLOps platform, as a self-hosted alternative to Google Cloud's Vertex AI on an Ubuntu server. The setup uses Docker Compose and Traefik to provide experiment tracking, pipelines, model registry, and Triton-based serving, covering the full ML lifecycle without vendor lock-in. Google Vertex AI is Google Cloud's managed ML platform with experiment tracking, training jobs, pipelines, a model registry, and endpoints, but it locks you into GCP-specific APIs and per-use billing. ClearML is an open-source MLOps platform that covers the same ground on any Docker host or Kubernetes cluster, capturing training metrics automatically with no code changes and keeping every byte of data on infrastructure you control. This guide deploys ClearML Server with Docker Compose and Traefik, registers an agent, runs an experiment, builds a pipeline, runs a hyperparameter sweep, and deploys a Triton-served model. By the end, you'll have a self-hosted Vertex AI replacement covering the full ML lifecycle. | GCP Vertex AI | ClearML Equivalent | Purpose | |---|---|---| | Vertex AI Workbench | ClearML Web UI | Browser-based monitoring and configuration | | Vertex AI Experiments | ClearML Experiment Manager | Automatic hyperparameter/metric/artifact tracking | | Vertex AI Training Job | ClearML Agent + Tasks | Any machine becomes a remote worker via queues | | Vertex AI Pipelines | ClearML Pipelines | Python-native DAGs, no separate compile step | | Vertex AI Model Registry | ClearML Model Repository | Versioned models with full lineage | | Vertex AI Endpoints | ClearML Serving Triton | Self-hosted inference with canary/A-B support | | Cloud Monitoring/Logging | ClearML Scalars/Plots | Built-in metrics and hardware dashboards | Prerequisite:Ubuntu host with Docker + Compose, DNS A records for app.clearml.example.com , api.clearml.example.com , files.clearml.example.com . NVIDIA Container Toolkit if you'll run GPU agents. 1. Raise Elasticsearch's virtual memory limit and restart Docker: bash $ echo "vm.max map count=524288" | sudo tee /etc/sysctl.d/99-clearml.conf $ sudo sysctl --system $ sudo systemctl restart docker 2. Create persistent data directories with the correct ownership: bash $ 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 3. Download the official Compose file: bash $ mkdir -p ~/clearml && cd ~/clearml $ curl -fsSL https://raw.githubusercontent.com/clearml/clearml-server/master/docker/docker-compose.yml -o docker-compose.yml 4. Edit it: comment out the ports: blocks under apiserver , webserver , and fileserver Traefik will handle external routing , and replace the networks: section with named bridges: networks: backend: name: clearml backend driver: bridge frontend: name: clearml frontend driver: bridge 5. Set the public hostnames: bash $ nano .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 6. Start the stack: bash $ docker compose up -d $ docker compose ps $ docker compose logs --tail 50 1. Create the Traefik directory and cert store: bash $ mkdir -p ~/clearml/traefik && cd ~/clearml/traefik $ mkdir -p letsencrypt && touch letsencrypt/acme.json && chmod 600 letsencrypt/acme.json 2. Set the ACME email: bash $ nano .env LETSENCRYPT EMAIL=admin@example.com 3. Create the Traefik Compose file: bash $ nano 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 4. Route each subdomain to its ClearML service: bash $ nano dynamic conf.yml http: routers: clearml-web: rule: "Host app.clearml.example.com " entryPoints: websecure service: clearml-web tls: {certResolver: le} clearml-api: rule: "Host api.clearml.example.com " entryPoints: websecure service: clearml-api tls: {certResolver: le} clearml-files: rule: "Host files.clearml.example.com " entryPoints: websecure service: clearml-files tls: {certResolver: le} services: clearml-web: loadBalancer: servers: {url: "http://clearml-webserver:80"} clearml-api: loadBalancer: servers: {url: "http://clearml-apiserver:8008"} clearml-files: loadBalancer: servers: {url: "http://clearml-fileserver:8081"} 5. Start Traefik and confirm certificates issued: bash $ docker compose up -d $ docker logs traefik 2 &1 | grep -i certificate https://app.clearml.example.com , enter a username and company name, and click 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" } } bash $ 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; press Enter to accept defaults for the rest. Then start the daemon: bash $ clearml-agent daemon --queue default --detached For GPU workloads: bash $ clearml-agent daemon --gpus 0,1 --queue default --detached Confirm registration in Workers & Queues → Workers . bash $ source ~/clearml-agent/clearml venv/bin/activate $ pip install clearml scikit-learn joblib pandas $ clearml-init Paste the credentials block again when prompted. bash $ mkdir -p ~/clearml/experiments && cd ~/clearml/experiments $ nano 01 first experiment.py python 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, classification report task = Task.init project name='ClearML Tutorial', task name='01 First Experiment', tags= 'tutorial', 'random-forest' 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 .fit X train, y train y pred = clf.predict X test accuracy = accuracy score y test, y pred report = classification report y test, y pred, target names=iris.target names, output dict=True logger = task.get logger logger.report scalar title='Performance', series='Accuracy', value=accuracy, iteration=1 for label, metrics in report.items : if isinstance metrics, dict : for metric name, value in metrics.items : logger.report scalar title=f'Class: {label}', series=metric name, value=value, iteration=1 model path = 'iris rf model.pkl' joblib.dump clf, model path task.upload artifact name='trained model', artifact object=model path task.close bash $ python3 01 first experiment.py The output prints a task URL — open it to see execution details, hyperparameters, artifacts, console output, and scalar charts under the ClearML Tutorial project. bash $ nano 02 pipeline.py python from clearml import PipelineController def step one pickle data url : import pickle, pandas as pd from clearml import StorageManager pickle data url = pickle data url or 'https://github.com/allegroai/events/raw/master/odsc20-east/generic/iris dataset.pkl' local iris pkl = StorageManager.get local copy remote url=pickle data url with open local iris 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 return LogisticRegression solver='lbfgs', max iter=1000 .fit X train, y train if name == ' main ': pipe = PipelineController project='ClearML Tutorial', name='02 Pipeline Experiment', version='1.0', add pipeline tags=True pipe.add parameter 'url', 'https://github.com/allegroai/events/raw/master/odsc20-east/generic/iris dataset.pkl' pipe.add function step 'step one', step one, function kwargs={'pickle data url': '${pipeline.url}'}, function return= 'data frame' pipe.add function step 'step two', step two, function kwargs={'data frame': '${step one.data frame}'}, function return= 'processed data' pipe.add function step 'step three', step three, function kwargs={'data': '${step two.processed data}'}, function return= 'model' pipe.start locally run pipeline steps locally=True bash $ python3 02 pipeline.py bash $ nano 03 hpo.py python 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 = optimizer.get top experiments 1 if top: print top 0 .id, top 0 .get parameters as dict .get 'General', {} bash $ python3 03 hpo.py Monitor progress in the Web UI under ClearML Tutorial . bash $ cd ~/clearml $ git clone https://github.com/clearml/clearml-serving.git $ pip install clearml-serving $ clearml-serving create --name "serving-example" Set the serving .env : bash $ nano 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" Start the Triton serving stack: bash $ cd ~/clearml/clearml-serving/docker $ docker compose --env-file .env -f docker-compose-triton.yml up -d Train and register a sample model: bash $ pip install -r ~/clearml/clearml-serving/examples/pytorch/requirements.txt $ python3 ~/clearml/clearml-serving/examples/pytorch/train pytorch mnist.py Copy the Model ID from the task's Artifacts tab, then add the endpoint: bash $ 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 inference: bash $ 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"}' bash $ 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/ In the Web UI: confirm the agent shows under Workers & Queues , the first experiment has metrics/artifacts, and cloning + re-enqueuing an experiment gets picked up by the agent. | Vertex AI concept | ClearML replacement | |---|---| google.cloud.aiplatform experiment tracking | clearml.Task — auto-captures Git state, deps, uncommitted changes | | Custom Training Jobs | clearml.Task + task.execute remotely , or enqueue via UI | | Vertex AI Workbench notebooks | Standard Jupyter/JupyterHub — Task.init works identically | | Kubeflow-based Vertex Pipelines | PipelineController / @pipeline — Python-native, no compile step | | Vizier hyperparameter tuning | HyperParameterOptimizer — runs on your own agents | | Model Registry | OutputModel — full lineage to source experiment | | Vertex AI Endpoints | ClearML Serving + Triton — canary/A-B on your own infra | | Batch Prediction Jobs | No direct equivalent — migrate to standard Tasks writing to object storage | | GCS data access | ClearML works with GCS paths directly, or migrate to any storage | | IAM + ADC auth | API keys via clearml.conf or CLEARML API ACCESS KEY / SECRET KEY | AIP env conventions | Not needed — agents use standard Python/Docker, no Vertex-specific vars | | MLflow tracking calls | ClearML's MLflow compatibility layer routes mlflow.log without a rewrite | ClearML is running with tracking, agents, pipelines, HPO, and Triton serving. From here you can: For the full guide with additional tips, visit the original article on Vultr Docs .