XOOMAR
Photorealistic tech workspace showing an AI model deployment pipeline with containers, cloud nodes, and automation.
TechnologyJune 16, 2026· 17 min read· By XOOMAR Insights Team

Ship a Sklearn Model With Docker and CI/CD Without Chaos

Share

XOOMAR Intelligence

Analyst Take

Updated on June 16, 2026

Deploying a model is where a notebook experiment becomes a usable service. If you want to deploy sklearn model Docker workflows reliably, the practical path is to train a scikit-learn model, save it, expose it through an API, package the API and model into a Docker image, and automate build/test/push steps in CI/CD.

This tutorial uses the same proven stack covered in the source material: scikit-learn for training, joblib or pickle for persistence, FastAPI and Pydantic for request validation, Uvicorn for serving, and Docker for consistent deployment across environments.


What You Need Before Deploying a Scikit-Learn Model

Before you deploy an sklearn model with Docker, make sure the project has four working pieces: a trained model, a prediction interface, a dependency list, and Docker installed locally.

The source examples consistently frame Docker as the solution to a common deployment problem: code that works on one machine may fail elsewhere because of differences in operating systems, package versions, or configuration. Docker packages the application, dependencies, libraries, and settings into a portable container so the service runs consistently across environments.

Key idea: Docker does not replace model training or API design. It packages your trained model, serving code, dependencies, and runtime instructions into a repeatable deployment unit.

Required tools and files

Requirement Why you need it Source-grounded example
Python Used to train the model and serve predictions Source examples use Python with scikit-learn and FastAPI
scikit-learn Trains classical ML models Examples use RandomForestClassifier, DecisionTreeClassifier, and RandomForestRegressor
joblib or pickle Saves and loads trained models Sources show joblib.dump, joblib.load, and pickle.dump
FastAPI Exposes the model as a REST API Sources create /predict or /classify endpoints
Pydantic Validates incoming request bodies Sources define request schemas with BaseModel
Uvicorn Runs the FastAPI app Sources run uvicorn app:app or uvicorn main:app
Docker Builds and runs the containerized service Sources use Dockerfile, docker build, and docker run
Docker Hub or container registry Stores and shares built images Sources show docker tag, docker push, and docker pull

A clean project layout keeps training code, model artifacts, API code, and container files separate. The source examples use structures with models/, app/, requirements.txt, and Dockerfile.

sklearn-docker-service/
├── app/
│   ├── __init__.py
│   └── main.py
├── models/
│   └── iris_model.joblib
├── train_model.py
├── requirements.txt
├── Dockerfile
└── README.md

Why this structure works:

  • Training code: train_model.py creates the model artifact.
  • Model artifact: models/iris_model.joblib is loaded by the API.
  • API code: app/main.py defines request validation and prediction routes.
  • Dependencies: requirements.txt keeps package installation reproducible.
  • Container config: Dockerfile defines how Docker builds and runs the service.

Train and Save a Scikit-Learn Model

The first step is to train a model and persist it to disk. The sources use small datasets to keep the deployment tutorial focused: the Iris dataset, dummy blob classification data, and the scikit-learn diabetes dataset.

For this tutorial, we’ll use the Iris dataset, which appears in multiple source examples. It has four features:

  • Sepal length
  • Sepal width
  • Petal length
  • Petal width

Those features classify observations into three classes: Iris Setosa, Iris Versicolour, and Iris Virginica.

Create train_model.py

# train_model.py

from pathlib import Path

from joblib import dump
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


MODEL_DIR = Path("models")
MODEL_PATH = MODEL_DIR / "iris_model.joblib"


def train_model():
    # Load Iris dataset
    iris = datasets.load_iris()
    X = iris.data
    y = iris.target

    # Split dataset into train and test sets
    X_train, X_test, y_train, y_test = train_test_split(
        X,
        y,
        test_size=0.3,
        random_state=42
    )

    # Train model
    model = RandomForestClassifier(random_state=42)
    model.fit(X_train, y_train)

    # Evaluate model
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print(f"Accuracy: {accuracy}")

    # Save model
    MODEL_DIR.mkdir(exist_ok=True)
    dump(model, MODEL_PATH)
    print(f"Model saved to {MODEL_PATH}")


if __name__ == "__main__":
    train_model()

Run it locally:

python train_model.py

This produces a saved model file:

models/iris_model.joblib

joblib vs pickle for saving sklearn models

The source material shows both joblib and pickle being used to persist scikit-learn models.

Method Used in source examples for Typical role in this tutorial
joblib Saving scikit-learn pipelines and classifiers with dump() / load() Used here for iris_model.joblib
pickle Saving a trained diabetes model as diabetes_model.pkl Valid alternative if your team already uses pickle

Practical note: Use one persistence method consistently across training and serving. If you save with joblib.dump(), load with joblib.load().


Create a Prediction API for the Model

Once the model is saved, the next step is to expose it through an API. The source examples use FastAPI because it supports request validation through Pydantic and automatically generates OpenAPI documentation.

FastAPI also provides interactive documentation at /docs and ReDoc documentation at /redoc, as shown in the source examples.

Install API dependencies

Create a requirements.txt file:

fastapi
uvicorn
pydantic
scikit-learn
joblib

The source examples recommend keeping the dependency list focused instead of blindly freezing every package in your local environment. One source uses a filtered command like this:

pip freeze | grep -E "fastapi|joblib|pydantic|scikit-learn|uvicorn" > requirements.txt

If you use conda, the source example shows exporting selected packages and converting them into pip-style pins:

conda list --export | grep -E "fastapi|joblib|pydantic|scikit-learn|uvicorn" | sed -E "s/(.*)=(.*)=.*$/\1==\2/" > requirements.txt

Create app/main.py

# app/main.py

import os
from pathlib import Path
from typing import List

from fastapi import FastAPI
from pydantic import BaseModel, conlist
from joblib import load


MODEL_PATH = os.getenv("MODEL_PATH", "models/iris_model.joblib")


class IrisRequest(BaseModel):
    data: List[conlist(float, min_length=4, max_length=4)]


class PredictionResponse(BaseModel):
    prediction: List[int]
    probabilities: List[List[float]]


app = FastAPI(
    title="Iris ML API",
    description="API for serving a scikit-learn Iris classifier",
    version="1.0.0"
)


@app.on_event("startup")
def load_model():
    model_file = Path(MODEL_PATH)

    if not model_file.exists():
        raise FileNotFoundError(f"Model file not found: {MODEL_PATH}")

    app.model = load(model_file)


@app.get("/health")
def health_check():
    return {"status": "ok"}


@app.post("/predict", response_model=PredictionResponse)
def predict(request: IrisRequest):
    prediction = app.model.predict(request.data).tolist()
    probabilities = app.model.predict_proba(request.data).tolist()

    return PredictionResponse(
        prediction=prediction,
        probabilities=probabilities
    )

Why this API shape works

This API follows patterns from the source examples:

  • Model loaded at startup: Sources load the model when the FastAPI app starts instead of loading it on every request.
  • Pydantic validation: The request schema requires each Iris sample to contain exactly four float values.
  • POST endpoint: Sources use /predict or /classify as a POST endpoint for inference.
  • Structured response: Sources return predictions plus probabilities or log probabilities.
  • Health endpoint: Added here as a minimal operational endpoint so containers and deployment systems can check whether the API is running.

Important warning: Make sure the feature order in your API matches the feature order used during training. The Iris model expects four values in the same order used by the dataset: sepal length, sepal width, petal length, and petal width.

Run the API locally before Docker

uvicorn app.main:app --host 127.0.0.1 --port 5000

Then open:

http://127.0.0.1:5000/docs

FastAPI should show an interactive Swagger UI where you can test /predict.

You can also test with curl:

curl -X POST "http://127.0.0.1:5000/predict" \
  -H "Content-Type: application/json" \
  -d '{"data":[[4.8,3.0,1.4,0.3],[6.0,2.9,4.5,1.5]]}'

Expected response shape:

{
  "prediction": [0, 1],
  "probabilities": [
    [1.0, 0.0, 0.0],
    [0.0, 0.9, 0.1]
  ]
}

Your exact probability values may differ depending on the trained model.


Write a Dockerfile for the ML Service

Now that the API works locally, package it with Docker. This is the core step when you want to deploy sklearn model Docker workflows across machines without manually recreating the environment.

The sources show multiple Dockerfile patterns:

Source pattern Base image Runtime command
Minimal Python app python:3.11-slim CMD ["python", "model.py"]
FastAPI sklearn service python:3.8-slim-buster CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
FastAPI-optimized image tiangolo/uvicorn-gunicorn:python3.8-slim Uses FastAPI-oriented environment variables

For this tutorial, we’ll use a slim Python image and run Uvicorn directly, matching the simple Dockerfile style shown in the sources.

Create Dockerfile

# Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY app /app/app
COPY models /app/models

ENV MODEL_PATH=models/iris_model.joblib
ENV PORT=80

CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]

What each Dockerfile instruction does

Instruction Purpose
FROM Specifies the base image. Source examples use slim Python images such as python:3.11-slim and python:3.8-slim-buster.
WORKDIR Sets the working directory inside the container.
COPY requirements.txt Adds the dependency file before the application code.
RUN pip install Installs FastAPI, Uvicorn, scikit-learn, joblib, and Pydantic.
COPY app / COPY models Copies the API code and saved model into the image.
ENV Sets default runtime configuration such as model path and port.
CMD Starts the FastAPI service with Uvicorn.

Build tip from the source material: Tag the image during build so you do not have to rely on Docker-generated image IDs.


Run and Test the Container Locally

Build the Docker image:

docker build --tag sklearn-iris-api:latest .

Run the container:

docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest

This maps local port 8000 to container port 80, similar to the source examples that run FastAPI containers and expose them through Docker port mapping.

Test the health endpoint:

curl http://localhost:8000/health

Expected response:

{"status":"ok"}

Test the prediction endpoint:

curl -X POST "http://localhost:8000/predict" \
  -H "Content-Type: application/json" \
  -d '{"data":[[4.8,3.0,1.4,0.3],[6.0,2.9,4.5,1.5]]}'

You should receive a JSON response with predictions and class probabilities.

Useful Docker commands for local testing

Command Purpose
docker build --tag sklearn-iris-api:latest . Builds the image from the current directory
docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest Runs the API container in detached mode
docker logs sklearn-iris-api Shows container logs
docker stop sklearn-iris-api Stops the running container
docker rm sklearn-iris-api Removes the stopped container

Add Environment Variables and Health Checks

The basic source examples often hard-code values such as model paths and ports. For a more deployable service, parameterize them with environment variables and add a health check endpoint.

This is especially useful when the same image moves between local testing, staging, and production-like environments.

Environment variables used in this tutorial

Variable Default Purpose
MODEL_PATH models/iris_model.joblib Tells the API where to load the model from
PORT 80 Sets the port Uvicorn listens on inside the container

Run with a custom model path:

docker run -d \
  -p 8000:80 \
  -e MODEL_PATH=models/iris_model.joblib \
  --name sklearn-iris-api \
  sklearn-iris-api:latest

Add a Docker health check

Docker supports HEALTHCHECK instructions in the Dockerfile. The source material does not provide a health-check implementation, but the API pattern above includes a simple /health route so a container runtime can verify the service is responding.

To use curl inside the health check, the image must include it. A minimal version looks like this:

# Dockerfile with health check support

FROM python:3.11-slim

WORKDIR /app

RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY app /app/app
COPY models /app/models

ENV MODEL_PATH=models/iris_model.joblib
ENV PORT=80

HEALTHCHECK CMD curl --fail http://localhost:${PORT}/health || exit 1

CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]

Check health status:

docker ps

Look for the container health state in the output.

Operational caution: Keep the health endpoint lightweight. It should confirm the API process is responsive; avoid expensive prediction calls as a basic liveness check.


Set Up a Basic CI/CD Deployment Pipeline

A CI/CD pipeline for this project should automate the same commands you already tested locally: install dependencies, train or verify the model artifact, build the Docker image, run a container test, tag the image, and push it to a registry such as Docker Hub.

The sources specifically show Docker Hub commands:

docker login
docker tag ml-model yourdockerhubusername/ml-model
docker push yourdockerhubusername/ml-model
docker pull yourdockerhubusername/ml-model
docker run yourdockerhubusername/ml-model

They also show tagging during build:

docker build --tag my-name/my-sklearn-api-service:latest .
docker push my-name/my-sklearn-api-service:latest

CI/CD stages for an sklearn Docker deployment

Stage What it does Command pattern
Install Installs Python dependencies pip install -r requirements.txt
Train or verify model Creates or confirms model artifact exists python train_model.py
Build image Builds Docker image docker build --tag ... .
Run container test Starts container and checks /health docker run ... + curl
Prediction smoke test Sends a sample request to /predict curl -X POST ...
Tag image Tags image for registry docker tag ...
Push image Uploads image to Docker Hub or registry docker push ...

Basic CI/CD script

Because CI/CD platforms differ, here is a platform-neutral shell script that can be adapted to your chosen system:

#!/usr/bin/env bash
set -e

IMAGE_NAME="${IMAGE_NAME:-sklearn-iris-api}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"

echo "Installing dependencies..."
pip install -r requirements.txt

echo "Training model..."
python train_model.py

echo "Building Docker image..."
docker build --tag "${FULL_IMAGE}" .

echo "Starting container..."
docker rm -f sklearn-iris-api-test || true
docker run -d -p 8000:80 --name sklearn-iris-api-test "${FULL_IMAGE}"

echo "Waiting briefly for API startup..."
sleep 5

echo "Checking health endpoint..."
curl --fail http://localhost:8000/health

echo "Running prediction smoke test..."
curl --fail -X POST "http://localhost:8000/predict" \
  -H "Content-Type: application/json" \
  -d '{"data":[[4.8,3.0,1.4,0.3]]}'

echo "Cleaning up test container..."
docker rm -f sklearn-iris-api-test

echo "Pipeline completed successfully."

Optional Docker Hub push step

If your CI/CD environment has registry credentials configured, add:

docker login

docker tag sklearn-iris-api:latest yourdockerhubusername/sklearn-iris-api:latest

docker push yourdockerhubusername/sklearn-iris-api:latest

Then another environment can pull and run it:

docker pull yourdockerhubusername/sklearn-iris-api:latest

docker run -d -p 8000:80 yourdockerhubusername/sklearn-iris-api:latest

CI/CD principle: The deployment pipeline should run the same build and smoke-test steps every time so the Docker image pushed to the registry is known to start and respond.

This is the automation layer that completes a practical deploy sklearn model Docker workflow.


Common Deployment Mistakes and How to Avoid Them

Even a small scikit-learn API can fail in production-like environments if the model, dependencies, or request schema are not handled carefully.

1. Loading the model on every request

The FastAPI source examples load the model during startup rather than inside the prediction function. This avoids repeatedly reading the model file for every inference request.

Avoid it like this:

@app.on_event("startup")
def load_model():
    app.model = load("models/iris_model.joblib")

Do not do this inside every request:

@app.post("/predict")
def predict(request: IrisRequest):
    model = load("models/iris_model.joblib")
    return model.predict(request.data)

2. Sending the wrong number of features

The Iris examples require four features per observation. The source API example uses Pydantic’s constrained lists to enforce exactly four values.

Avoid it like this:

class IrisRequest(BaseModel):
    data: List[conlist(float, min_length=4, max_length=4)]

If a client sends only three values, FastAPI validation rejects the request before it reaches the model.

3. Mixing up feature order

A model trained on [sepal length, sepal width, petal length, petal width] expects that same order at prediction time.

Bad request shape:

{
  "data": [[1.4, 0.3, 4.8, 3.0]]
}

Correct request shape:

{
  "data": [[4.8, 3.0, 1.4, 0.3]]
}

4. Copying too many dependencies into requirements.txt

One source explicitly warns against blindly requiring every package installed on your system. Instead, filter the dependencies to only those needed by the service.

For this tutorial, the focused dependency list is:

fastapi
uvicorn
pydantic
scikit-learn
joblib

5. Forgetting to include the model file in the Docker image

Your API may work locally because models/iris_model.joblib exists on your machine. It will fail in Docker if the file is not copied into the image.

Correct Dockerfile line:

COPY models /app/models

6. Not testing the container before pushing it

The sources verify Docker builds by running the container and calling the API endpoint with curl.

A good minimum test is:

docker run -d -p 8000:80 --name sklearn-iris-api sklearn-iris-api:latest

curl --fail http://localhost:8000/health

curl --fail -X POST "http://localhost:8000/predict" \
  -H "Content-Type: application/json" \
  -d '{"data":[[4.8,3.0,1.4,0.3]]}'

7. Assuming a notebook is a deployment

The source material emphasizes that a model in a notebook is not yet useful to other systems. To make it usable, expose it as an API and package it with its dependencies.

A deployable service needs:

  • Saved artifact: The trained model file.
  • API contract: A predictable request and response format.
  • Runtime: FastAPI and Uvicorn.
  • Container: Docker image with code, model, and dependencies.
  • Verification: Local container test and CI/CD smoke test.

Bottom Line

To deploy sklearn model Docker workflows effectively, keep the system simple and repeatable: train the model, save it with joblib or pickle, serve it through FastAPI, validate inputs with Pydantic, containerize it with Docker, and test the running container before pushing it to Docker Hub or another registry.

The strongest pattern across the source material is consistency. Docker packages your ML service and its dependencies into a portable unit, while FastAPI gives you a structured prediction API with automatic documentation at /docs and /redoc.

For a basic CI/CD setup, automate the same commands you trust locally: install dependencies, train or verify the model, build the image, run the container, call /health, smoke-test /predict, tag the image, and push it to a registry.


FAQ

How do I deploy a scikit-learn model with Docker?

Train the model with scikit-learn, save it with joblib or pickle, create a FastAPI prediction endpoint, write a Dockerfile that installs dependencies and copies the model into the image, then build and run the container with docker build and docker run.

Should I use FastAPI for serving a scikit-learn model?

FastAPI is used throughout the source examples because it supports REST endpoints, Pydantic request validation, async-compatible route handlers, and automatically generated API documentation at /docs and /redoc.

Should I save my sklearn model with joblib or pickle?

Both are shown in the source material. joblib is used in examples that save scikit-learn classifiers and pipelines, while pickle is used in an example that saves a diabetes progression model. Use the same tool for saving and loading.

What should be in requirements.txt?

For the FastAPI sklearn service in this tutorial, the dependency file includes fastapi, uvicorn, pydantic, scikit-learn, and joblib. One source recommends filtering dependencies instead of blindly exporting every installed package.

How do I test the Dockerized model locally?

Run the container with a port mapping, then call the health and prediction endpoints:

docker run -d -p 8000:80 sklearn-iris-api:latest

curl http://localhost:8000/health

curl -X POST "http://localhost:8000/predict" \
  -H "Content-Type: application/json" \
  -d '{"data":[[4.8,3.0,1.4,0.3]]}'
How does CI/CD fit into sklearn Docker deployment?

A basic CI/CD pipeline automates the same steps you run locally: install dependencies, train or verify the model, build the Docker image, run a container test, call the API with curl, tag the image, and push it to Docker Hub or another container registry.

Sources & References

Content sourced and verified on June 16, 2026

  1. 1
    Deploying sklearn Models via FastAPI and Docker

    https://www.auroria.io/deploying-sklearn-models-via-fastapi-and-docker/

  2. 2
    Step-by-Step Guide to Deploying ML Models with Docker - KDnuggets

    https://www.kdnuggets.com/step-by-step-guide-to-deploying-ml-models-with-docker

  3. 3
    Serve a machine learning model using Sklearn, FastAPI and Docker

    https://medium.com/analytics-vidhya/serve-a-machine-learning-model-using-sklearn-fastapi-and-docker-85aabf96729b

  4. 4
    A Step-by-Step Guide to Containerizing and Deploying Machine Learning Models with Docker!

    https://dev.to/pavanbelagatti/a-step-by-step-guide-to-containerizing-and-deploying-machine-learning-models-with-docker-21al

  5. 5
    Step-by-Step Guide to Deploying Machine Learning Models with FastAPI and Docker - MachineLearningMastery.com

    https://machinelearningmastery.com/step-by-step-guide-to-deploying-machine-learning-models-with-fastapi-and-docker/

  6. 6
    Deploy Machine Learning Models with Docker: A Practical Perspective

    https://www.usaii.org/ai-insights/deploy-machine-learning-models-with-docker-a-practical-perspective

XOOMAR

Written by

XOOMAR Insights Team

Research and Editorial Desk

The XOOMAR Insights Team pairs automated research with human editorial judgment. We track hundreds of sources across technology, fintech, trading, SaaS, and cybersecurity, cross-check the facts, and explain what happened, why it matters, and what to watch next. We do not just rewrite headlines. Every article is fact-checked and scored for reliability before it goes live, and we link back to the original sources so you can verify anything yourself.

Related Articles

Futuristic server lab comparing simple ML API endpoint with scalable distributed AI pipelineTechnology

Ray Serve vs FastAPI Exposes the ML API Scaling Trap

FastAPI wins for simple model APIs. Ray Serve wins when batching, autoscaling, GPUs, or multi-model pipelines start to matter.

Jun 16, 202622 min
Small AI team in a sleek workspace managing streamlined MLOps pipelines and model monitoring.Technology

No-Bloat MLOps Tools Small Teams Can Ship With in 2026

Small teams don't need enterprise MLOps sprawl. A lean 2026 stack can track, deploy, monitor, and update models without platform drag.

Jun 16, 202625 min
Three AI chatbot builders compete around a glowing company document hub in a futuristic workspace.Technology

No-Code RAG Chatbot Builders Fight for Company Docs

No-code RAG tools can work, but Dify, automation stacks, and LangChain trade speed for control in very different ways.

Jun 16, 202619 min
Futuristic workspace with organized accelerator application workflows on glowing abstract screens.Technology

Kill Deadline Chaos With Accelerator Tracking Tools

Stop running accelerator applications from inboxes. Centralize deadlines, decks, intros, interviews, and follow-ups before things slip.

Jun 9, 202621 min
Engineers in a futuristic AI operations hub compare competing model deployment pipelines.Technology

BentoML vs KServe vs Seldon Splits Kubernetes Teams

KServe fits Kubernetes-native teams, Seldon handles inference graphs, and BentoML wins on Python-first packaging and fast iteration.

Jun 16, 202624 min
NFT trader using tax software to organize wallet activity, cost basis, and crypto reports.Fintech

NFT Tax Software That Saves Traders From Cost Basis Hell

NFT traders need tax software that can track wallets, cost basis, DeFi activity, and CPA-ready reports before filings get messy.

Jun 16, 202624 min
Analyst organizing chaotic DeFi wallet transactions into clean crypto tax visuals on modern fintech devicesFintech

Wallet Chaos Tests the Best DeFi Crypto Tax Software

DeFi tax tools can miss costly labels. The best choice depends on wallet imports, swaps, staking, LPs, NFTs, and cleanup support.

Jun 16, 202623 min
Smartphone banking app with glowing subaccount compartments for budgeting in a modern fintech sceneFintech

Best Digital Banks With Subaccounts to Tame Budgets

Subaccounts clean up budgeting, but many are just labels. The right digital bank depends on how much real separation you need.

Jun 16, 202623 min
Two travel payment app concepts in an airport lounge, comparing short installments with larger trip financing.Fintech

Klarna vs Affirm Travel Pits Pay in 4 Against Big Loans

Klarna fits shorter, flexible travel payments. Affirm is stronger for big trips, longer terms, and travel-brand acceptance.

Jun 16, 202620 min
Bullish crypto trading floor with rising charts and spring sunrise after bitcoin selloffTrading

$59K Bitcoin Low Sparks Wall Street's Crypto Spring Call

Standard Chartered says bitcoin's $59K low likely ended the selloff after ETFs, Strategy buying and oil all turned in bulls' favor.

Jun 16, 20269 min