diff --git a/Makefile b/Makefile
index fe6c99339..e248fcdcd 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ IMG_VERSION ?= main
 # container image repository
 IMG_REPO ?= model-registry
 # container image
-IMG := ${IMG_REGISTRY}/$(IMG_ORG)/$(IMG_REPO)
+IMG ?= ${IMG_REGISTRY}/$(IMG_ORG)/$(IMG_REPO)
 
 model-registry: build
 
diff --git a/csi/GET_STARTED.md b/csi/GET_STARTED.md
new file mode 100644
index 000000000..eda9124ff
--- /dev/null
+++ b/csi/GET_STARTED.md
@@ -0,0 +1,249 @@
+# Get Started
+
+Embark on your journey with this custom storage initializer by exploring a simple hello-world example. Learn how to seamlessly integrate and leverage the power of this tool in just a few steps.
+
+## Prerequisites
+
+* Install [Kind](https://kind.sigs.k8s.io/docs/user/quick-start) (Kubernetes in Docker)ΒΆ to run local Kubernetes cluster with Docker container nodes.
+* Install the [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/), which allows you to run commands against Kubernetes clusters.
+* Install the [Kustomize](https://kustomize.io/), which allows you to customize app configuration.
+
+## Environment Preparation
+
+We assume all [prerequisites](#prerequisites) are satisfied at this point.
+
+### Create the environment
+
+1. After having kind installed, create a kind cluster with:
+```bash
+kind create cluster
+```
+
+2. Configure `kubectl` to use kind context
+```bash
+kubectl config use-context kind-kind
+```
+
+3. Setup local deployment of *Kserve* using the provided *Kserve quick installation* script
+```bash
+curl -s "https://raw.githubusercontent.com/kserve/kserve/release-0.11/hack/quick_install.sh" | bash
+```
+
+4. Install *model registry* in the local cluster
+
+[Optional ]Use model registry with local changes:
+
+```bash
+TAG=$(git rev-parse HEAD) && \
+MR_IMG=quay.io/$USER/model-registry:$TAG && \
+make -C ../ IMG_ORG=$USER IMG_VERSION=$TAG image/build && \
+kind load docker-image $MR_IMG
+```
+
+then:
+
+```bash
+bash ./hack/install_modelregistry.sh -i $MR_IMG
+```
+
+> _NOTE_: If you want to use a remote image you can simply remove the `-i` option
+
+> _NOTE_: The `./hack/install_modelregistry.sh` will make some change to [base/kustomization.yaml](../manifests/kustomize/base/kustomization.yaml) that you DON'T need to commit!!
+
+5. [Optional] Use local container image for CSI
+
+```bash
+IMG=quay.io/$USER/model-registry-storage-initializer:$(git rev-parse HEAD) && make IMG=$IMG docker-build && kind load docker-image $IMG
+```
+
+## First InferenceService with ModelRegistry URI
+
+In this tutorial, you will deploy an InferenceService with a predictor that will load a model indexed into the model registry, the indexed model refers to a scikit-learn model trained with the [iris](https://archive.ics.uci.edu/ml/datasets/iris) dataset. This dataset has three output class: Iris Setosa, Iris Versicolour, and Iris Virginica.
+
+You will then send an inference request to your deployed model in order to get a prediction for the class of iris plant your request corresponds to.
+
+Since your model is being deployed as an InferenceService, not a raw Kubernetes Service, you just need to provide the storage location of the model using the `model-registry://` URI format and it gets some super powers out of the box.
+
+
+### Register a Model into ModelRegistry
+
+Apply `Port Forward` to the model registry service in order to being able to interact with it from the outside of the cluster.
+```bash
+kubectl port-forward --namespace kubeflow svc/model-registry-service 8080:8080
+```
+
+And then (in another terminal):
+```bash
+export MR_HOSTNAME=localhost:8080
+```
+
+Then, in the same terminal where you exported `MR_HOSTNAME`, perform the following actions:
+1. Register an empty `RegisteredModel`
+
+```bash
+curl --silent -X 'POST' \
+  "$MR_HOSTNAME/api/model_registry/v1alpha2/registered_models" \
+  -H 'accept: application/json' \
+  -H 'Content-Type: application/json' \
+  -d '{
+  "description": "Iris scikit-learn model",
+  "name": "iris"
+}'
+```
+
+Expected output:
+```bash
+{"createTimeSinceEpoch":"1709287882361","customProperties":{},"description":"Iris scikit-learn model","id":"1","lastUpdateTimeSinceEpoch":"1709287882361","name":"iris"}
+```
+
+2. Register the first `ModelVersion`
+
+```bash
+curl --silent -X 'POST' \
+  "$MR_HOSTNAME/api/model_registry/v1alpha2/model_versions" \
+  -H 'accept: application/json' \
+  -H 'Content-Type: application/json' \
+  -d '{
+  "description": "Iris model version v1",
+  "name": "v1",
+  "registeredModelID": "1"
+}'
+```
+
+Expected output:
+```bash
+{"createTimeSinceEpoch":"1709287890365","customProperties":{},"description":"Iris model version v1","id":"2","lastUpdateTimeSinceEpoch":"1709287890365","name":"v1"}
+```
+
+3. Register the raw `ModelArtifact`
+
+This artifact defines where the actual trained model is stored, i.e., `gs://kfserving-examples/models/sklearn/1.0/model`
+
+```bash
+curl --silent -X 'POST' \
+  "$MR_HOSTNAME/api/model_registry/v1alpha2/model_versions/2/artifacts" \
+  -H 'accept: application/json' \
+  -H 'Content-Type: application/json' \
+  -d '{
+  "description": "Model artifact for Iris v1",
+  "uri": "gs://kfserving-examples/models/sklearn/1.0/model",
+  "state": "UNKNOWN",
+  "name": "iris-model-v1",
+  "modelFormatName": "sklearn",
+  "modelFormatVersion": "1",
+  "artifactType": "model-artifact"
+}'
+```
+
+Expected output:
+```bash
+{"artifactType":"model-artifact","createTimeSinceEpoch":"1709287972637","customProperties":{},"description":"Model artifact for Iris v1","id":"1","lastUpdateTimeSinceEpoch":"1709287972637","modelFormatName":"sklearn","modelFormatVersion":"1","name":"iris-model-v1","state":"UNKNOWN","uri":"gs://kfserving-examples/models/sklearn/1.0/model"}
+```
+
+> _NOTE_: double check the provided IDs are the expected ones.
+
+### Apply the `ClusterStorageContainer` resource
+
+Retrieve the model registry service and MLMD port:
+```bash
+MODEL_REGISTRY_SERVICE=model-registry-service
+MODEL_REGISTRY_REST_PORT=$(kubectl get svc/$MODEL_REGISTRY_SERVICE -n kubeflow --output jsonpath='{.spec.ports[0].targetPort}' )
+```
+
+Apply the cluster-scoped `ClusterStorageContainer` CR to setup configure the `model registry storage initilizer` for `model-registry://` URI formats.
+
+```bash
+kubectl apply -f - <<EOF
+apiVersion: "serving.kserve.io/v1alpha1"
+kind: ClusterStorageContainer
+metadata:
+  name: mr-initializer
+spec:
+  container:
+    name: storage-initializer
+    image: $IMG
+    env:
+    - name: MODEL_REGISTRY_BASE_URL
+      value: "$MODEL_REGISTRY_SERVICE.kubeflow.svc.cluster.local:$MODEL_REGISTRY_REST_PORT"
+    - name: MODEL_REGISTRY_SCHEME
+      value: "http"
+    resources:
+      requests:
+        memory: 100Mi
+        cpu: 100m
+      limits:
+        memory: 1Gi
+        cpu: "1"
+  supportedUriFormats:
+    - prefix: model-registry://
+
+EOF
+```
+
+> _NOTE_: as `$IMG` you could use either the one created during [env preparation](#environment-preparation) or any other remote img in the container registry.
+
+### Create an `InferenceService`
+
+1. Create a namespace
+```bash
+kubectl create namespace kserve-test
+```
+
+2. Create the `InferenceService`
+```bash
+kubectl apply -n kserve-test -f - <<EOF
+apiVersion: "serving.kserve.io/v1beta1"
+kind: "InferenceService"
+metadata:
+  name: "iris-model"
+spec:
+  predictor:
+    model:
+      modelFormat:
+        name: sklearn
+      storageUri: "model-registry://iris/v1"
+EOF
+```
+
+3. Check `InferenceService` status
+```bash
+kubectl get inferenceservices iris-model -n kserve-test
+```
+
+4. Determine the ingress IP and ports
+
+```bash
+kubectl get svc istio-ingressgateway -n istio-system
+```
+
+And then:
+```bash
+INGRESS_GATEWAY_SERVICE=$(kubectl get svc --namespace istio-system --selector="app=istio-ingressgateway" --output jsonpath='{.items[0].metadata.name}')
+kubectl port-forward --namespace istio-system svc/${INGRESS_GATEWAY_SERVICE} 8081:80
+```
+
+After that (in another terminal):
+```bash
+export INGRESS_HOST=localhost
+export INGRESS_PORT=8081
+```
+
+5. Perform the inference request
+
+Prepare the input data:
+```bash
+cat <<EOF > "/tmp/iris-input.json"
+{
+  "instances": [
+    [6.8,  2.8,  4.8,  1.4],
+    [6.0,  3.4,  4.5,  1.6]
+  ]
+}
+EOF
+```
+
+If you do not have DNS, you can still curl with the ingress gateway external IP using the HOST Header.
+```bash
+SERVICE_HOSTNAME=$(kubectl get inferenceservice iris-model -n kserve-test -o jsonpath='{.status.url}' | cut -d "/" -f 3)
+curl -v -H "Host: ${SERVICE_HOSTNAME}" -H "Content-Type: application/json" "http://${INGRESS_HOST}:${INGRESS_PORT}/v1/models/iris-v1:predict" -d @/tmp/iris-input.json
+```
\ No newline at end of file
diff --git a/csi/README.md b/csi/README.md
index 30404ce4c..329a8189c 100644
--- a/csi/README.md
+++ b/csi/README.md
@@ -1 +1,89 @@
-TODO
\ No newline at end of file
+# Model Registry Custom Storage Initializer
+
+This is a Model Registry specific implementation of a KServe Custom Storage Initializer (CSI). 
+More details on what `Custom Storage Initializer` is can be found in the [KServe doc](https://kserve.github.io/website/0.11/modelserving/storage/storagecontainers/).
+
+## Implementation
+
+The Model Registry CSI is a simple Go executable that basically takes two positional arguments:
+1. __Source URI__: identifies the `storageUri` set in the `InferenceService`, this must be a model-registry custom URI, i.e., `model-registry://...` 
+2. __Deestination Path__: the location where the model should be stored, e.g., `/mnt/models`
+
+The core logic of this CSI is pretty simple and it consists of three main steps:
+1. Parse the custom URI in order to extract `registered model name` and `model version`
+2. Query the model registry in order to retrieve the original model location (e.g., `http`, `s3`, `gcs` and so on)
+3. Use `github.com/kserve/kserve/pkg/agent/storage` pkg to actually download the model from well-known protocols.
+
+### Workflow
+
+The below sequence diagram should highlight the workflow when this CSI is injected into the KServe pod deployment.
+
+```mermaid
+sequenceDiagram
+    actor U as User
+    participant MR as Model Registry
+    participant KC as KServe Controller
+    participant MD as Model Deployment (Pod)
+    participant MRSI as Model Registry Storage Initializer
+    U->>+MR: Register ML Model
+    MR-->>-U: Indexed Model
+    U->>U: Create InferenceService CR
+    Note right of U: The InferenceService should<br/>point to the model registry<br/>indexed model, e.g.,:<br/> model-registry://<model>/<version>
+    KC->>KC: React to InferenceService creation
+    KC->>+MD: Create Model Deployment
+    MD->>+MRSI: Initialization (Download Model)
+    MRSI->>MRSI: Parse URI
+    MRSI->>+MR: Fetch Model Metadata
+    MR-->>-MRSI: Model Metadata
+    Note over MR,MRSI: The main information that is fetched is the artifact URI which specifies the real model location, e.g.,: https://.. or s3://...
+    MRSI->>MRSI: Download Model
+    Note right of MRSI: The storage initializer will use<br/> the KServe default providers<br/> to download the model<br/> based on the artifact URI
+    MRSI-->>-MD: Downloaded Model
+    MD->>-MD: Deploy Model
+```
+
+
+## Get Started
+
+Please look at [Get Started](./GET_STARTED.md) guide for a very simple quickstart that showcases how this custom storage initializer can be used for ML models serving operations.
+
+## Development
+
+### Build the executable
+
+You can just run:
+```bash
+make build
+```
+
+Which wil create the executable under `bin/mr-storage-initializer`.
+
+### Run the executable
+
+You can run `main.go` (without building the executable) by running:
+```bash
+./bin/mr-storage-initializer "model-registry://model/version" "./"
+```
+
+or directly running the `main.go` skipping the previous step:
+```bash
+make SOURCE_URI=model-registry://model/version DEST_PATH=./ run
+```
+
+> _NOTE_: a Model Registry service should be up and running at `localhost:8080`.
+
+### Build container image
+
+Run:
+```bash
+make docker-build
+```
+
+By default the container image name is `quay.io/${USER}/model-registry-storage-initializer:latest` but it can be overridden providing the `IMG` env variable, e.g., `make IMG=abc/ORG/NAME:TAG docker-build`.
+
+### Push container image
+
+Issue the following command:
+```bash
+make [IMG=..] docker-push
+```
\ No newline at end of file
diff --git a/csi/hack/install_modelregistry.sh b/csi/hack/install_modelregistry.sh
new file mode 100755
index 000000000..8f63c31ff
--- /dev/null
+++ b/csi/hack/install_modelregistry.sh
@@ -0,0 +1,48 @@
+set -e
+set -o xtrace
+
+############################################################
+# Help                                                     #
+############################################################
+Help() {
+  # Display Help
+  echo "ModelRegistry install script."
+  echo
+  echo "Syntax: [-n NAMESPACE] [-i IMAGE]"
+  echo "options:"
+  echo "n Namespace."
+  echo "i Model registry image."
+  echo
+}
+
+MR_ROOT=".."
+
+namespace=kubeflow
+image=quay.io/opendatahub/model-registry:latest
+while getopts ":hn:i:" option; do
+   case $option in
+      h) # display Help
+         Help
+         exit;;
+      n) # override namespace
+          namespace=$OPTARG;;
+      i) # override model registry image
+          image=$OPTARG;;
+     \?) # Invalid option
+         echo "Error: Invalid option"
+         exit;;
+   esac
+done
+
+# Create namespace if not already existing
+if ! kubectl get namespace "$namespace" &> /dev/null; then
+   kubectl create namespace $namespace
+fi
+# Apply model-registry kustomize manifests
+echo Using model registry image: $image
+cd $MR_ROOT/manifests/kustomize/base && kustomize edit set image quay.io/opendatahub/model-registry:latest=${image} && cd -
+kubectl -n $namespace apply -k "$MR_ROOT/manifests/kustomize/overlays/postgres"
+
+# Wait for model registry deployment
+modelregistry=$(kubectl get pod -n kubeflow --selector="component=model-registry-server" --output jsonpath='{.items[0].metadata.name}')
+kubectl wait --for=condition=Ready pod/$modelregistry -n $namespace --timeout=5m
\ No newline at end of file