diff --git a/.drone/drone.yml b/.drone/drone.yml
deleted file mode 100644
index 58ef37c..0000000
--- a/.drone/drone.yml
+++ /dev/null
@@ -1,168 +0,0 @@
-kind: pipeline
-type: docker
-name: linux-amd64
-
-platform:
-  arch: amd64
-  os: linux
-
-steps:
-- name: test
-  image: docker.io/drycc/go-dev
-  pull: always
-  privileged: true
-  commands:
-  - make docker-build test
-  environment:
-    VERSION: ${DRONE_TAG:-latest}-linux-amd64
-    DEV_REGISTRY: ${DEV_REGISTRY:-docker.io}
-    DRYCC_REGISTRY: ${DRYCC_REGISTRY:-docker.io}
-  when:
-    event:
-    - push
-    - tag
-    - pull_request
-  volumes:
-  - name: image_registries
-    path: /etc/containers/registries.conf
-
-- name: publish
-  image: docker.io/drycc/go-dev
-  pull: always
-  privileged: true
-  commands:
-  - echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
-  - make docker-build docker-immutable-push
-  environment:
-    VERSION: ${DRONE_TAG:-latest}-linux-amd64
-    DEV_REGISTRY:
-      from_secret: dev_registry
-    DRYCC_REGISTRY:
-      from_secret: drycc_registry
-    DOCKER_USERNAME:
-      from_secret: docker_username
-    DOCKER_PASSWORD:
-      from_secret: docker_password
-  when:
-    event:
-    - push
-    - tag
-  volumes:
-  - name: image_registries
-    path: /etc/containers/registries.conf
-
-trigger:
-  event:
-  - push
-  - tag
-  - pull_request
-
-volumes:
-- name: image_registries
-  host:
-    path: /etc/containers/registries.conf
-
----
-kind: pipeline
-type: docker
-name: linux-arm64
-
-platform:
-  arch: arm64
-  os: linux
-
-steps:
-- name: publish
-  image: docker.io/drycc/go-dev
-  pull: always
-  privileged: true
-  commands:
-  - echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
-  - make docker-build docker-immutable-push
-  environment:
-    VERSION: ${DRONE_TAG:-latest}-linux-arm64
-    DEV_REGISTRY:
-      from_secret: dev_registry
-    DRYCC_REGISTRY:
-      from_secret: drycc_registry
-    DOCKER_USERNAME:
-      from_secret: docker_username
-    DOCKER_PASSWORD:
-      from_secret: docker_password
-  volumes:
-  - name: image_registries
-    path: /etc/containers/registries.conf
-
-trigger:
-  event:
-  - push
-  - tag
-
-volumes:
-- name: image_registries
-  host:
-    path: /etc/containers/registries.conf
-
----
-kind: pipeline
-type: docker
-name: manifest
-
-steps:
-- name: generate manifest
-  image: docker.io/library/alpine
-  pull: always
-  commands:
-  - sed -i "s/docker.io/$${DRYCC_REGISTRY}/g" .drone/manifest.tmpl
-  environment:
-    DRYCC_REGISTRY:
-      from_secret: drycc_registry
-
-- name: publish
-  image: plugins/manifest
-  settings:
-    spec: .drone/manifest.tmpl
-    username:
-      from_secret: docker_username
-    password:
-      from_secret: docker_password
-  environment:
-    DEV_REGISTRY:
-      from_secret: dev_registry
-    DRYCC_REGISTRY:
-      from_secret: drycc_registry
-
-trigger:
-  event:
-  - push
-  - tag
-
-depends_on:
-- linux-amd64
-- linux-arm64
-
----
-kind: pipeline
-type: docker
-name: chart
-
-steps:
-- name: generate chart
-  image: docker.io/drycc/python-dev
-  commands:
-  - IMAGE_TAG=$([ ! -z $DRONE_TAG ] && echo ${DRONE_TAG:1} || echo \"canary\")
-  - sed -i "s/image_tag:\ \"canary\"/image_tag:\ $IMAGE_TAG/g" charts/registry/values.yaml
-  - helm package charts/registry --version ${DRONE_TAG:-v1.0.0}
-  - curl -u $CHARTMUSEUM_USERNAME:$CHARTMUSEUM_PASSWORD -F chart=@registry-${DRONE_TAG:-v1.0.0}.tgz "$CHARTMUSEUM_API/api/$([ -z $DRONE_TAG ] && echo testing || echo stable)/charts"
-  environment:
-    CHARTMUSEUM_USERNAME:
-      from_secret: chartmuseum_username
-    CHARTMUSEUM_PASSWORD:
-      from_secret: chartmuseum_password
-    CHARTMUSEUM_API:
-      from_secret: chartmuseum_api
-
-trigger:
-  event:
-  - push
-  - tag
diff --git a/.drone/manifest.tmpl b/.drone/manifest.tmpl
deleted file mode 100644
index 6b5a36e..0000000
--- a/.drone/manifest.tmpl
+++ /dev/null
@@ -1,18 +0,0 @@
-image: docker.io/drycc/registry:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}canary{{/if}}
-{{#if build.tags}}
-tags:
-{{#each build.tags}}
-  - {{this}}
-{{/each}}
-{{/if}}
-manifests:
-  -
-    image: docker.io/drycc/registry:{{#if build.tag}}{{build.tag}}-{{else}}latest-{{/if}}linux-amd64
-    platform:
-      architecture: amd64
-      os: linux
-  -
-    image: docker.io/drycc/registry:{{#if build.tag}}{{build.tag}}-{{else}}latest-{{/if}}linux-arm64
-    platform:
-      architecture: arm64
-      os: linux
diff --git a/.gitignore b/.gitignore
index 5ed5b3e..e5361b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
-rootfs/opt/
 contrib/ci/tmp/
+.vscode/
+rootfs/opt/registry/sbin
\ No newline at end of file
diff --git a/.woodpecker/build-linux.yml b/.woodpecker/build-linux.yml
new file mode 100644
index 0000000..ef4ecfd
--- /dev/null
+++ b/.woodpecker/build-linux.yml
@@ -0,0 +1,35 @@
+matrix:
+  platform:
+    - linux/amd64
+    - linux/arm64
+
+labels:
+  type: exec
+  platform: ${platform}
+
+steps:
+- name: publish-linux
+  image: bash
+  commands:
+  - export VERSION=$([ -z $CI_COMMIT_TAG ] && echo latest || echo $CI_COMMIT_TAG)-$(sed 's#/#-#g' <<< $CI_SYSTEM_PLATFORM)
+  - echo $CONTAINER_PASSWORD | podman login $DRYCC_REGISTRY --username $CONTAINER_USERNAME --password-stdin > /dev/null 2>&1
+  - make podman-build podman-immutable-push
+  environment:
+    CODENAME:
+      from_secret: codename
+    DEV_REGISTRY:
+      from_secret: dev_registry
+    DRYCC_REGISTRY:
+      from_secret: drycc_registry
+    CONTAINER_USERNAME:
+      from_secret: container_username
+    CONTAINER_PASSWORD:
+      from_secret: container_password
+  when:
+    event:
+    - push
+    - tag
+    - cron
+
+depends_on:
+- test-linux
\ No newline at end of file
diff --git a/.woodpecker/chart.yaml b/.woodpecker/chart.yaml
new file mode 100644
index 0000000..cfcd3ec
--- /dev/null
+++ b/.woodpecker/chart.yaml
@@ -0,0 +1,33 @@
+labels:
+  type: exec
+  platform: linux/amd64
+
+steps:
+- name: generate-chart
+  image: bash
+  commands:
+  - export VERSION=$(sed 's#v##' <<< $CI_COMMIT_TAG)
+  - export IMAGE_TAG=$([ ! -z $CI_COMMIT_TAG ] && echo \"$VERSION\" || echo \"canary\")
+  - export APP_VERSION=$([ -z $CI_COMMIT_TAG ] && echo $CI_COMMIT_SHA || echo $VERSION)
+  - export CHART_VERSION=$([ -z $CI_COMMIT_TAG ] && echo 1.0.0 || echo $VERSION)
+  - sed -i "s/imageTag:\ \"canary\"/imageTag:\ $IMAGE_TAG/g" charts/$${CI_REPO_NAME}/values.yaml
+  - helm package -u charts/$${CI_REPO_NAME} --version $CHART_VERSION --app-version $APP_VERSION
+  - echo $CONTAINER_PASSWORD | helm registry login $DRYCC_REGISTRY -u $CONTAINER_USERNAME --password-stdin
+  - helm push $${CI_REPO_NAME}-$CHART_VERSION.tgz oci://$DRYCC_REGISTRY/$([ -z $CI_COMMIT_TAG ] && echo charts-testing || echo charts)
+  environment:
+    DEV_REGISTRY:
+      from_secret: dev_registry
+    DRYCC_REGISTRY:
+      from_secret: drycc_registry
+    CONTAINER_USERNAME:
+      from_secret: container_username
+    CONTAINER_PASSWORD:
+      from_secret: container_password
+  when:
+    event:
+    - push
+    - tag
+    - cron
+
+depends_on:
+- manifest
diff --git a/.woodpecker/manifest.tmpl b/.woodpecker/manifest.tmpl
new file mode 100644
index 0000000..8739535
--- /dev/null
+++ b/.woodpecker/manifest.tmpl
@@ -0,0 +1,18 @@
+image: registry.drycc.cc/drycc/{{project}}:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}canary{{/if}}
+{{#if build.tags}}
+tags:
+{{#each build.tags}}
+  - {{this}}
+{{/each}}
+{{/if}}
+manifests:
+  -
+    image: registry.drycc.cc/drycc/{{project}}:{{#if build.tag}}{{build.tag}}-{{else}}latest-{{/if}}linux-amd64
+    platform:
+      architecture: amd64
+      os: linux
+  -
+    image: registry.drycc.cc/drycc/{{project}}:{{#if build.tag}}{{build.tag}}-{{else}}latest-{{/if}}linux-arm64
+    platform:
+      architecture: arm64
+      os: linux
diff --git a/.woodpecker/manifest.yml b/.woodpecker/manifest.yml
new file mode 100644
index 0000000..19fc70c
--- /dev/null
+++ b/.woodpecker/manifest.yml
@@ -0,0 +1,43 @@
+labels:
+  type: exec
+  platform: linux/amd64
+
+steps:
+- name: generate-manifest
+  image: bash
+  commands:
+  - sed -i "s/{{project}}/$${CI_REPO_NAME}/g" .woodpecker/manifest.tmpl
+  - sed -i "s/registry.drycc.cc/$${DRYCC_REGISTRY}/g" .woodpecker/manifest.tmpl
+  environment:
+    DRYCC_REGISTRY:
+      from_secret: drycc_registry
+  when:
+    event:
+    - tag
+    - push
+    - cron
+
+- name: publish-manifest
+  image: bash
+  commands:
+  - podman run --rm
+    -e PLUGIN_SPEC=.woodpecker/manifest.tmpl
+    -e PLUGIN_USERNAME=$CONTAINER_USERNAME
+    -e PLUGIN_PASSWORD=$CONTAINER_PASSWORD
+    -e DRONE_TAG=$CI_COMMIT_TAG
+    -v $(pwd):$(pwd)
+    -w $(pwd)
+    docker.io/plugins/manifest
+  environment:
+    CONTAINER_USERNAME:
+      from_secret: container_username
+    CONTAINER_PASSWORD:
+      from_secret: container_password
+  when:
+    event:
+    - tag
+    - push
+    - cron
+
+depends_on:
+- build-linux
diff --git a/.woodpecker/test-linux.yml b/.woodpecker/test-linux.yml
new file mode 100644
index 0000000..194832f
--- /dev/null
+++ b/.woodpecker/test-linux.yml
@@ -0,0 +1,24 @@
+matrix:
+  platform:
+  - linux/amd64
+  - linux/arm64
+
+labels:
+  type: exec
+  platform: ${platform}
+
+steps:
+- name: test-linux
+  image: bash
+  commands:
+  - make test
+  environment:
+    CODENAME:
+      from_secret: codename
+    DEV_REGISTRY:
+      from_secret: dev_registry
+  when:
+    event:
+    - push
+    - tag
+    - cron
diff --git a/Dockerfile b/Dockerfile
index 7d18242..e9e37c3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,24 +1,55 @@
-FROM docker.io/minio/mc:latest as mc
+ARG CODENAME
 
-
-FROM docker.io/drycc/go-dev:latest AS build
+FROM registry.drycc.cc/drycc/go-dev:latest AS build
 ARG LDFLAGS
-ADD . /app
+ADD . /workspace
 RUN export GO111MODULE=on \
-  && cd /app \
-  && CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o /usr/local/bin/registry main.go \
-  && upx -9 --brute /usr/local/bin/registry
+  && cd /workspace \
+  && CGO_ENABLED=0 init-stack go build -ldflags "${LDFLAGS}" -o /bin/start-registry main.go \
+  && upx -9 --brute /bin/start-registry
+
+
+FROM registry.drycc.cc/drycc/base:${CODENAME}
 
+ARG DRYCC_UID=1001 \
+  DRYCC_GID=1001 \
+  DRYCC_HOME_DIR=/var/lib/registry \
+  JQ_VERSION="1.7.1" \
+  NGINX_VERSION="1.29.1" \
+  RCLONE_VERSION="1.71.1" \
+  REGISTRY_VERSION="3.0.0"
 
-FROM docker.io/library/registry:2.7
+RUN groupadd drycc --gid ${DRYCC_GID} \
+  && useradd drycc -u ${DRYCC_UID} -g ${DRYCC_GID} -s /bin/bash -m -d ${DRYCC_HOME_DIR} \
+  && install-packages apache2-utils \
+  && install-stack jq $JQ_VERSION \
+  && install-stack nginx ${NGINX_VERSION} \
+  && install-stack rclone $RCLONE_VERSION \
+  && install-stack registry $REGISTRY_VERSION \
+  && rm -rf \
+      /usr/share/doc \
+      /usr/share/man \
+      /usr/share/info \
+      /usr/share/locale \
+      /var/lib/apt/lists/* \
+      /var/log/* \
+      /var/cache/debconf/* \
+      /etc/systemd \
+      /lib/lsb \
+      /lib/udev \
+      /usr/lib/`echo $(uname -m)`-linux-gnu/gconv/IBM* \
+      /usr/lib/`echo $(uname -m)`-linux-gnu/gconv/EBC* \
+  && mkdir -p /usr/share/man/man{1..8} \
+  && chown -R ${DRYCC_UID}:${DRYCC_GID} /opt/drycc
 
-COPY rootfs /
-COPY --from=mc /usr/bin/mc /bin/mc
-COPY --from=build /usr/local/bin/registry /opt/registry/sbin/registry
+COPY --from=build /bin/start-registry /bin/start-registry
+COPY --chown=${DRYCC_UID}:${DRYCC_GID} rootfs/bin/ /bin/
+COPY --chown=${DRYCC_UID}:${DRYCC_GID} rootfs/opt/drycc/nginx /opt/drycc/nginx
+COPY --chown=${DRYCC_UID}:${DRYCC_GID} rootfs/config-example.yml /opt/drycc/registry/etc/config.yml
 
-RUN apk add --no-cache jq bash \
-  && chmod +x /bin/create_bucket /bin/normalize_storage 
+ENV OTEL_TRACES_EXPORTER=none \
+  DRYCC_REGISTRY_CONFIG=/opt/drycc/registry/etc/config.yml
 
-VOLUME ["/var/lib/registry"]
-CMD ["/opt/registry/sbin/registry"]
+USER ${DRYCC_UID}
+VOLUME ["${DRYCC_HOME_DIR}"]
 EXPOSE 5000
diff --git a/Makefile b/Makefile
index 1dc469b..7fce73e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 # Short name: Short name, following [a-zA-Z_], used all over the place.
 # Some uses for short name:
-# - Docker image name
+# - Container image name
 # - Kubernetes service, deployment, pod names
 SHORT_NAME := registry
 DRYCC_REGISTRY ?= ${DEV_REGISTRY}
@@ -16,8 +16,8 @@ SHELL_SCRIPTS = $(wildcard rootfs/bin/* _scripts/*.sh contrib/ci/*.sh)
 # The following variables describe the containerized development environment
 # and other build options
 DEV_ENV_IMAGE := ${DEV_REGISTRY}/drycc/go-dev
-DEV_ENV_WORK_DIR := /go/src/${REPO_PATH}
-DEV_ENV_PREFIX := docker run --rm -v ${CURDIR}:${DEV_ENV_WORK_DIR} -w ${DEV_ENV_WORK_DIR}
+DEV_ENV_WORK_DIR := /opt/drycc/go/src/${REPO_PATH}
+DEV_ENV_PREFIX := podman run --rm -v ${CURDIR}:${DEV_ENV_WORK_DIR} -w ${DEV_ENV_WORK_DIR}
 DEV_ENV_CMD := ${DEV_ENV_PREFIX} ${DEV_ENV_IMAGE}
 LDFLAGS := "-s -w -X main.version=${VERSION}"
 BINDIR := ./rootfs/opt/registry/sbin
@@ -29,31 +29,28 @@ endif
 all:
 	@echo "Use a Makefile to control top-level building of the project."
 
-build: check-docker
+build: check-podman
 	mkdir -p ${BINDIR}
 	$(MAKE) build-binary
 
 # For cases where we're building from local
 # We also alter the RC file to set the image name.
-docker-build: check-docker
-	docker build ${DOCKER_BUILD_FLAGS} -t ${IMAGE} --build-arg LDFLAGS=${LDFLAGS} .
-	docker tag ${IMAGE} ${MUTABLE_IMAGE}
-
-docker-buildx: check-docker
-	docker buildx build --platform ${PLATFORM} -t ${IMAGE} --build-arg LDFLAGS=${LDFLAGS} . --push
+podman-build: check-podman
+	podman build --build-arg CODENAME=${CODENAME} -t ${IMAGE} --build-arg LDFLAGS=${LDFLAGS} .
+	podman tag ${IMAGE} ${MUTABLE_IMAGE}
 
 build-binary:
 	${DEV_ENV_CMD} go build -ldflags ${LDFLAGS} -o $(BINDIR)/${SHORT_NAME} main.go
 	$(call check-static-binary,$(BINDIR)/${SHORT_NAME})
 	${DEV_ENV_CMD} upx -9 --brute $(BINDIR)/${SHORT_NAME}
 
-test: check-docker test-style
+test: check-podman podman-build test-style
 	contrib/ci/test.sh ${IMAGE}
 
 test-style:
 	${DEV_ENV_CMD} shellcheck $(SHELL_SCRIPTS)
 
-deploy: check-kubectl docker-build docker-push
+deploy: check-kubectl podman-build podman-push
 	kubectl --namespace=drycc patch deployment drycc-$(SHORT_NAME) --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"$(IMAGE)"}]'
 
-.PHONY: all build build-binary docker-build test test-style deploy
+.PHONY: all build build-binary podman-build test test-style deploy
diff --git a/README.md b/README.md
index d3c5a7a..ae7eea3 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Drycc Registry v2
 
-[![Build Status](https://drone.drycc.cc/api/badges/drycc/registry/status.svg)](https://drone.drycc.cc/drycc/registry)
+[![Build Status](https://woodpecker.drycc.cc/api/badges/drycc/registry/status.svg)](https://woodpecker.drycc.cc/drycc/registry)
 [![Go Report Card](https://goreportcard.com/badge/github.com/drycc/registry)](https://goreportcard.com/report/github.com/drycc/registry)
 
 
@@ -12,11 +12,19 @@ We welcome your input! If you have feedback, please submit an [issue][issues]. I
 
 # About
 
-The registry is a [Docker registry](https://docs.docker.com/registry/) component for use in Kubernetes. While it's intended for use inside of the Drycc open source [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service), it's flexible enough to be used as a standalone pod on any Kubernetes cluster.
+Registry consists of two components, namely the proxy component and the registry component.
 
-If you decide to use this component standalone, you can host your own Docker registry in your own Kubernetes cluster.
+## Proxy
 
-The Docker image that this repository builds is based on [the official Docker v2 registry image](https://github.com/docker/distribution).
+The proxy component is a proxy deployed on every Kubernetes worker node, proxying all requests to the Drycc Workflow [registry][registry]. This allows the worker nodes daemons to communicate to the registry over localhost, bypassing the need for adding the `--insecure-registry` flag to the daemons.
+
+## Registry
+
+The registry component is a [Container registry](https://github.com/distribution/distribution) component for use in Kubernetes. While it's intended for use inside of the Drycc open source [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service), it's flexible enough to be used as a standalone pod on any Kubernetes cluster.
+
+If you decide to use this component standalone, you can host your own Container registry in your own Kubernetes cluster.
+
+The Container image that this repository builds is based on [the official Container v2 registry image](https://github.com/distribution/distribution).
 
 # Development
 
@@ -49,7 +57,7 @@ $ export IMAGE_PREFIX=youruser/             # if using Quay or Dockerhub
 To build and push the image run:
 
 ```console
-$ make docker-build docker-push
+$ make podman-build podman-push
 ```
 
 To deploy the image via patching the registry deployment run:
diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml
index bbe1197..cd8abff 100644
--- a/charts/registry/Chart.yaml
+++ b/charts/registry/Chart.yaml
@@ -1,7 +1,13 @@
 name: registry
 home: https://github.com/drycc/registry
-version: v1.0.0
-description: Docker registry for Drycc Workflow.
+apiVersion: v2
+appVersion: 1.0.0
+dependencies:
+  - name: common
+    repository: oci://registry.drycc.cc/charts
+    version: ~1.1.2
+description: Container registry for Drycc Workflow.
 maintainers:
   - name: Drycc Team
     email: engineering@drycc.com
+version: v1.1.0
diff --git a/charts/registry/templates/_helper.tpl b/charts/registry/templates/_helper.tpl
new file mode 100644
index 0000000..cd87b72
--- /dev/null
+++ b/charts/registry/templates/_helper.tpl
@@ -0,0 +1,71 @@
+{{- define "registry.envs" }}
+env:
+- name: REGISTRY_STORAGE_DELETE_ENABLED
+  value: "true"
+- name: REGISTRY_LOG_LEVEL
+  value: info
+- name: "REGISTRY_HTTP_SECRET"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: secret
+- name: "DRYCC_REGISTRY_REDIRECT"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: redirect
+- name: "DRYCC_REGISTRY_USERNAME"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: username
+- name: "DRYCC_REGISTRY_PASSWORD"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: password
+{{- if (.Values.storageEndpoint) }}
+- name: "DRYCC_STORAGE_BUCKET"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: storage-bucket
+- name: "DRYCC_STORAGE_ENDPOINT"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: storage-endpoint
+- name: "DRYCC_STORAGE_ACCESSKEY"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: storage-accesskey
+- name: "DRYCC_STORAGE_SECRETKEY"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: storage-secretkey
+- name: "DRYCC_STORAGE_PATH_STYLE"
+  valueFrom:
+    secretKeyRef:
+      name: registry-secret
+      key: storage-path-style
+{{- else if .Values.storage.enabled  }}
+- name: "DRYCC_STORAGE_BUCKET"
+  value: "registry"
+- name: "DRYCC_STORAGE_ENDPOINT"
+  value: http://drycc-storage:9000
+- name: "DRYCC_STORAGE_ACCESSKEY"
+  valueFrom:
+    secretKeyRef:
+      name: storage-creds
+      key: accesskey
+- name: "DRYCC_STORAGE_SECRETKEY"
+  valueFrom:
+    secretKeyRef:
+      name: storage-creds
+      key: secretkey
+- name: "DRYCC_STORAGE_PATH_STYLE"
+  value: "true"
+{{- end }}
+{{- end }}
diff --git a/charts/registry/templates/registry-cronjob-daily.yaml b/charts/registry/templates/registry-cronjob-daily.yaml
new file mode 100644
index 0000000..442841b
--- /dev/null
+++ b/charts/registry/templates/registry-cronjob-daily.yaml
@@ -0,0 +1,46 @@
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+  name: drycc-registry-cronjob-daily
+  labels:
+    heritage: drycc
+  annotations:
+    component.drycc.cc/version: {{ .Values.imageTag }}
+spec:
+  schedule: "0 0 * * *"
+  concurrencyPolicy: {{ .Values.concurrencyPolicy }}
+  successfulJobsHistoryLimit: 1
+  failedJobsHistoryLimit: 1
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          restartPolicy: OnFailure
+          serviceAccount: drycc-controller
+          initContainers:
+          - name: drycc-registry-init
+            image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/python-dev:latest
+            imagePullPolicy: {{.Values.imagePullPolicy}}
+            args:
+            - netcat
+            - -v
+            - -u
+            - $(DRYCC_STORAGE_ENDPOINT)
+            {{- include "registry.envs" . | indent 12 }}
+          containers:
+          - image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/registry:{{.Values.imageTag}}
+            imagePullPolicy: {{.Values.imagePullPolicy}}
+            name: drycc-registry-garbage-collect
+            {{- if .Values.diagnosticMode.enabled }}
+            command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 14 }}
+            args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 14 }}
+            {{- else }}
+            args:
+            - /usr/bin/env
+            - bash
+            - -ec
+            - |
+              # run garbage collect
+              start-registry garbage-collect ${DRYCC_REGISTRY_CONFIG} --dry-run --delete-untagged
+            {{- end }}
+            {{- include "registry.envs" . | indent 12 }}
diff --git a/charts/registry/templates/registry-deployment.yaml b/charts/registry/templates/registry-deployment.yaml
index facffc0..dc362e3 100644
--- a/charts/registry/templates/registry-deployment.yaml
+++ b/charts/registry/templates/registry-deployment.yaml
@@ -1,4 +1,3 @@
-{{- if eq .Values.global.registry_location "on-cluster" }}
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -6,9 +5,9 @@ metadata:
   labels:
     heritage: drycc
   annotations:
-    component.drycc.cc/version: {{ .Values.image_tag }}
+    component.drycc.cc/version: {{ .Values.imageTag }}
 spec:
-  replicas: 1
+  replicas: {{ .Values.replicas }}
   strategy:
     rollingUpdate:
       maxSurge: 1
@@ -19,61 +18,78 @@ spec:
       app: drycc-registry
   template:
     metadata:
-      labels:
+      labels: {{- include "common.labels.standard" . | nindent 8 }}
         app: drycc-registry
     spec:
+      affinity:
+        podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset.type "component" "" "extraMatchLabels" .Values.podAffinityPreset.extraMatchLabels "topologyKey" "" "context" $) | nindent 10 }}
+        podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset.type "component" "" "extraMatchLabels" .Values.podAntiAffinityPreset.extraMatchLabels "topologyKey" "" "context" $) | nindent 10 }}
+        nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values ) | nindent 10 }}
       serviceAccount: drycc-registry
       initContainers:
       - name: drycc-registry-init
-        image: docker.io/drycc/python-dev:latest
-        imagePullPolicy: {{.Values.image_pull_policy}}
-        command:
-          - netcat
-          - -v
-          - -a
-          - $(DRYCC_MINIO_SERVICE_HOST):$(DRYCC_MINIO_SERVICE_PORT)
+        image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/python-dev:latest
+        imagePullPolicy: {{.Values.imagePullPolicy}}
+        args:
+        - netcat
+        - -v
+        - -u
+        - $(DRYCC_STORAGE_ENDPOINT)
+        {{- include "registry.envs" . | indent 8 }}
       containers:
       - name: drycc-registry
-        image: {{.Values.image_registry}}/{{.Values.image_org}}/registry:{{.Values.image_tag}}
-        imagePullPolicy: {{.Values.image_pull_policy}}
-{{- if or (.Values.limits_cpu) (.Values.limits_memory)}}
+        image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/registry:{{.Values.imageTag}}
+        imagePullPolicy: {{.Values.imagePullPolicy}}
+        {{- if .Values.diagnosticMode.enabled }}
+        command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 10 }}
+        args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 10 }}
+        {{- else }}
+        args:
+        - start-registry
+        {{- end }}
+        {{- with index .Values "resources" }}
         resources:
-          limits:
-{{- if (.Values.limits_cpu) }}
-            cpu: {{.Values.limits_cpu}}
-{{- end}}
-{{- if (.Values.limits_memory) }}
-            memory: {{.Values.limits_memory}}
-{{- end}}
-{{- end}}
+          {{- toYaml . | nindent 10 }}
+        {{- end }}
+        {{- include "registry.envs" . | indent 8 }}
+        {{- if not .Values.diagnosticMode.enabled }}
+        startupProbe:
+          tcpSocket:
+            port: 5000
+          initialDelaySeconds: 30
+          periodSeconds: 5
+          timeoutSeconds: 1
+          successThreshold: 1
+          failureThreshold: 5
         livenessProbe:
           httpGet:
-            path: /v2/
+            path: /
             port: 5000
-          initialDelaySeconds: 1
+          initialDelaySeconds: 30
+          periodSeconds: 5
           timeoutSeconds: 1
+          successThreshold: 1
+          failureThreshold: 5
         readinessProbe:
           httpGet:
-            path: /v2/
+            path: /
             port: 5000
-          initialDelaySeconds: 1
+          initialDelaySeconds: 30
+          periodSeconds: 5
           timeoutSeconds: 1
-        env:
-          - name: REGISTRY_STORAGE_DELETE_ENABLED
-            value: "true"
-          - name: REGISTRY_LOG_LEVEL
-            value: info
+          successThreshold: 1
+          failureThreshold: 5
+        {{- end }}
         ports:
-          - containerPort: 5000
+        - containerPort: 5000
+          name: http
         volumeMounts:
-          - name: registry-storage
-            mountPath: /var/lib/registry
-          - name: objectstorage-creds
-            mountPath: /var/run/secrets/drycc/objectstore/creds
-      volumes:
         - name: registry-storage
-          emptyDir: {}
-        - name: objectstorage-creds
-          secret:
-            secretName: objectstorage-keyfile
-{{- end }}
+          mountPath: /var/lib/registry
+      securityContext:
+        fsGroup: 1001
+        runAsGroup: 1001
+        runAsUser: 1001
+      volumes:
+      - name: registry-storage
+        emptyDir: {}
diff --git a/charts/registry/templates/registry-proxy-daemonset.yaml b/charts/registry/templates/registry-proxy-daemonset.yaml
new file mode 100644
index 0000000..c83a364
--- /dev/null
+++ b/charts/registry/templates/registry-proxy-daemonset.yaml
@@ -0,0 +1,98 @@
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: drycc-registry-proxy
+  labels:
+    heritage: drycc
+  annotations:
+    component.drycc.cc/version: {{ .Values.imageTag }}
+spec:
+  updateStrategy:
+    type: RollingUpdate
+  selector:
+    matchLabels:
+      app: drycc-registry-proxy
+      heritage: drycc
+  template:
+    metadata:
+      name: drycc-registry-proxy
+      labels:
+        heritage: drycc
+        app: drycc-registry-proxy
+    spec:
+      securityContext:
+        fsGroup: 1001
+        runAsGroup: 1001
+        runAsUser: 1001
+      initContainers:
+      - name: drycc-registry-init
+        image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/python-dev:latest
+        imagePullPolicy: {{.Values.imagePullPolicy}}
+        args:
+        - netcat
+        - -v
+        - -a
+        - $(DRYCC_REGISTRY_HOST)
+        env:
+        - name: "DRYCC_REGISTRY_HOST"
+          value: drycc-registry:5000
+      containers:
+      - name: drycc-registry-proxy
+        image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/registry:{{.Values.imageTag}}
+        imagePullPolicy: {{.Values.imagePullPolicy}}
+        {{- if .Values.diagnosticMode.enabled }}
+        command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 10 }}
+        args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 10 }}
+        {{- else }}
+        args:
+        - start-proxy
+        {{- end }}
+        {{- with index .Values "proxy" "resources" }}
+        resources:
+          {{- toYaml . | nindent 10 }}
+        {{- end }}
+        {{- if not .Values.diagnosticMode.enabled }}
+        startupProbe:
+          httpGet:
+            path: /
+            port: 8080
+          initialDelaySeconds: 30
+          periodSeconds: 5
+          timeoutSeconds: 1
+          successThreshold: 1
+          failureThreshold: 5
+        livenessProbe:
+          httpGet:
+            path: /
+            port: 8080
+          initialDelaySeconds: 30
+          periodSeconds: 5
+          timeoutSeconds: 1
+          successThreshold: 1
+          failureThreshold: 5
+        readinessProbe:
+          httpGet:
+            path: /
+            port: 8080
+          initialDelaySeconds: 30
+          periodSeconds: 5
+          timeoutSeconds: 1
+          successThreshold: 1
+          failureThreshold: 5
+        {{- end }}
+        env:
+        - name: "DRYCC_REGISTRY_HOST"
+          value: drycc-registry:5000
+        - name: "DRYCC_REGISTRY_USERNAME"
+          valueFrom:
+            secretKeyRef:
+              name: registry-secret
+              key: username
+        - name: "DRYCC_REGISTRY_PASSWORD"
+          valueFrom:
+            secretKeyRef:
+              name: registry-secret
+              key: password
+        ports:
+        - containerPort: 8080
+          hostPort: {{.Values.proxy.port}}
diff --git a/charts/registry/templates/registry-secret.yaml b/charts/registry/templates/registry-secret.yaml
new file mode 100644
index 0000000..9fa12be
--- /dev/null
+++ b/charts/registry/templates/registry-secret.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: registry-secret
+  labels:
+    heritage: drycc
+type: Opaque
+data:
+  secret: {{ include "common.secrets.lookup" (dict "secret" "registry-secret" "key" "secret" "defaultValue" (randAlphaNum 32) "context" $) }}
+  username: {{ include "common.secrets.lookup" (dict "secret" "registry-secret" "key" "username" "defaultValue" (.Values.username | default (randAlphaNum 32)) "context" $) }}
+  password: {{ include "common.secrets.lookup" (dict "secret" "registry-secret" "key" "password" "defaultValue" (.Values.password | default (randAlphaNum 32)) "context" $) }}
+  redirect: {{ .Values.redirect | b64enc }}
+  {{- if (.Values.storageEndpoint) }}
+  storage-bucket: {{ .Values.storageBucket | b64enc }}
+  storage-endpoint: {{ .Values.storageEndpoint | b64enc }}
+  storage-accesskey: {{ .Values.storageAccesskey | b64enc }}
+  storage-secretkey: {{ .Values.storageSecretkey | b64enc }}
+  storage-path-style: {{ .Values.storagePathStyle | b64enc }}
+  {{- end }}
diff --git a/charts/registry/templates/registry-service-account.yaml b/charts/registry/templates/registry-service-account.yaml
index f117ae3..a3b2b78 100644
--- a/charts/registry/templates/registry-service-account.yaml
+++ b/charts/registry/templates/registry-service-account.yaml
@@ -1,8 +1,6 @@
-{{- if eq .Values.global.registry_location "on-cluster" }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: drycc-registry
   labels:
     heritage: drycc
-{{- end }}
diff --git a/charts/registry/templates/registry-service.yaml b/charts/registry/templates/registry-service.yaml
index be960ac..fbafcf2 100644
--- a/charts/registry/templates/registry-service.yaml
+++ b/charts/registry/templates/registry-service.yaml
@@ -1,16 +1,17 @@
-{{- if eq .Values.global.registry_location "on-cluster" }}
 apiVersion: v1
 kind: Service
 metadata:
   name: drycc-registry
+  annotations:
+    prometheus.io/path: /metrics
+    prometheus.io/port: "9000"
+    prometheus.io/scrape: "true"
+    {{- with .Values.service.annotations }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
   labels:
     heritage: drycc
 spec:
-  ports:
-    - name: http
-      port: 80
-      targetPort: 5000
+  clusterIP: None
   selector:
     app: drycc-registry
-  sessionAffinity: ClientIP  
-{{- end }}
diff --git a/charts/registry/values.yaml b/charts/registry/values.yaml
index 14c748b..87a2895 100644
--- a/charts/registry/values.yaml
+++ b/charts/registry/values.yaml
@@ -1,6 +1,84 @@
-image_org: "drycc"
-image_pull_policy: "Always"
-image_tag: "canary"
-image_registry: "docker.io"
-# limits_cpu: "100m"
-# limits_memory: "50Mi"
+imageOrg: "drycc"
+imagePullPolicy: "Always"
+imageTag: "canary"
+imageRegistry: "registry.drycc.cc"
+
+## Enable diagnostic mode
+##
+diagnosticMode:
+  ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden)
+  ##
+  enabled: false
+  ## @param diagnosticMode.command Command to override all containers
+  ##
+  command:
+    - sleep
+  ## @param diagnosticMode.args Args to override all containers
+  ##
+  args:
+    - infinity
+
+nodeAffinityPreset:
+  key: "drycc.cc/node"
+  type: "soft"
+  values:
+  - "true"
+
+podAffinityPreset:
+  type: ""
+  extraMatchLabels:
+    security: "drycc-security"
+
+podAntiAffinityPreset:
+  type: "soft"
+  extraMatchLabels:
+    app: "drycc-registry"
+
+# registry replicas
+replicas: 1
+# registry storage redirect
+redirect: "false"
+
+proxy:
+  # host port for the registry proxy in the daemonset
+  port: 5555
+  resources: {}
+  # limits:
+  #   cpu: 200m
+  #   memory: 50Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 30Mi
+
+resources: {}
+  # limits:
+  #   cpu: 200m
+  #   memory: 50Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 30Mi
+
+concurrencyPolicy: "Replace"
+
+# The following parameters will no longer use the built-in storage component.
+storageBucket: "builder"
+storageEndpoint: ""
+storageAccesskey: ""
+storageSecretkey: ""
+storagePathStyle: "auto"
+
+storage:
+  enabled: true
+
+# Service
+service:
+  # Provide any additional service annotations
+  annotations: {}
+
+resources: {}
+  # limits:
+  #   cpu: 200m
+  #   memory: 50Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 30Mi
diff --git a/contrib/ci/test.sh b/contrib/ci/test.sh
index a0c143c..6be70b3 100755
--- a/contrib/ci/test.sh
+++ b/contrib/ci/test.sh
@@ -2,33 +2,73 @@
 
 set -eoxf pipefail
 
-CURRENT_DIR=$(cd "$(dirname "$0")"; pwd)
+DRYCC_STORAGE_ACCESSKEY=f4c4281665bc11ee8e0400163e04a9cd
+DRYCC_STORAGE_SECRETKEY=f4c4281665bc11ee8e0400163e04a9cd
 
-mkdir -p "${CURRENT_DIR}"/tmp/aws-user
-echo "us-east-1" > "${CURRENT_DIR}"/tmp/aws-user/region
-echo "registry-bucket" > "${CURRENT_DIR}"/tmp/aws-user/registry-bucket
-echo "1234567890123456789012345678901234567890" > "${CURRENT_DIR}"/tmp/aws-user/accesskey
-echo "1234567890123456789012345678901234567890" > "${CURRENT_DIR}"/tmp/aws-user/secretkey
 
-MINIO_JOB=$(docker run -d --name minio \
-  -v "${CURRENT_DIR}"/tmp/aws-user:/var/run/secrets/drycc/objectstore/creds \
-  drycc/minio:canary server /home/minio/)
+STORAGE_JOB=$(podman run -d --rm --entrypoint init-stack \
+  -e RUSTFS_ACCESS_KEY="${DRYCC_STORAGE_ACCESSKEY}" \
+  -e RUSTFS_SECRET_KEY="${DRYCC_STORAGE_SECRETKEY}" \
+  "${DEV_REGISTRY}"/drycc/storage:canary rustfs /data)
 
-sleep 5
-docker logs "${MINIO_JOB}"
+# wait for port
+STORAGE_IP=$(podman inspect --format "{{ .NetworkSettings.IPAddress }}" "${STORAGE_JOB}")
+echo -e "\\033[32m---> Waitting for ${STORAGE_IP}:9000\\033[0m"
+wait-for-port --host="${STORAGE_IP}" 9000
+echo -e "\\033[32m---> S3 service ${STORAGE_IP}:9000 ready...\\033[0m"
+podman logs "${STORAGE_JOB}"
 
-MINIO_IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" "${MINIO_JOB}")
+REGISTRY_JOB=$(podman run -d --rm \
+  -e DRYCC_REGISTRY_REDIRECT=false \
+  -e DRYCC_REGISTRY_USERNAME=admin \
+  -e DRYCC_REGISTRY_PASSWORD=admin \
+  -e DRYCC_STORAGE_BUCKET=registry \
+  -e DRYCC_STORAGE_ENDPOINT="http://${STORAGE_IP}:9000" \
+  -e DRYCC_STORAGE_ACCESSKEY="${DRYCC_STORAGE_ACCESSKEY}" \
+  -e DRYCC_STORAGE_SECRETKEY="${DRYCC_STORAGE_SECRETKEY}" \
+  -e DRYCC_STORAGE_PATH_STYLE=true \
+  "$1" start-registry)
 
-JOB=$(docker run --add-host minio:"${MINIO_IP}" \
-  -d \
-  -e DRYCC_MINIO_SERVICE_HOST=minio \
-  -e DRYCC_MINIO_SERVICE_PORT=9000 \
-  -v "${CURRENT_DIR}"/tmp/aws-user:/var/run/secrets/drycc/objectstore/creds \
-  "$1")
+# shellcheck disable=SC2317
+function clean_before_exit {
+  # delay before exiting, so stdout/stderr flushes through the logging system
+  podman kill "${REGISTRY_JOB}"
+  podman kill "${STORAGE_JOB}"
+  podman kill "${PROXY_JOB}"
+}
+trap clean_before_exit EXIT
 
 # let the registry run for a few seconds
-sleep 5
+REGISTRY_IP=$(podman inspect --format "{{ .NetworkSettings.IPAddress }}" "${REGISTRY_JOB}")
+echo -e "\\033[32m---> Waitting for ${REGISTRY_IP}:5000\\033[0m"
+wait-for-port --host="${REGISTRY_IP}" 5000
+echo -e "\\033[32m---> S3 service ${REGISTRY_IP}:5000 ready...\\033[0m"
+
+# proxy job
+PROXY_JOB=$(podman run -d \
+  -p 15555:8080 \
+  -e DRYCC_REGISTRY_HOST="${REGISTRY_IP}:5000" \
+  -e DRYCC_REGISTRY_USERNAME=admin \
+  -e DRYCC_REGISTRY_PASSWORD=admin \
+  "$1" start-proxy)
+
+# let the registry proxy run for a few seconds
+REGISTRY_PROXY_IP=$(podman inspect --format "{{ .NetworkSettings.IPAddress }}" "${PROXY_JOB}")
+echo -e "\\033[32m---> Waitting for ${REGISTRY_PROXY_IP}:8080\\033[0m"
+wait-for-port --host="${REGISTRY_PROXY_IP}" 8080
+echo -e "\\033[32m---> S3 service ${REGISTRY_PROXY_IP}:8080 ready...\\033[0m"
+
 # check that the registry is still up
-docker logs "${JOB}"
-docker ps -q --no-trunc=true | grep "${JOB}"
-docker rm -f "${JOB}" "${MINIO_JOB}"
+http_status_code=$(curl -X GET -s -o /dev/null -w "%{http_code}" "http://${REGISTRY_PROXY_IP}:8080/v2/")
+if [ "$http_status_code" != "200" ]; then
+    echo "Expected http status code: 200, actual: ${http_status_code}"
+    exit 1
+fi
+
+http_status_code=$(curl -X POST -s -o /dev/null -w "%{http_code}" "http://${REGISTRY_PROXY_IP}:8080/v2/")
+if [ "$http_status_code" != "403" ]; then
+    echo "Expected http status code: 403, actual: ${http_status_code}"
+    exit 1
+fi
+
+echo -e "\\033[32m---> All test success...\\033[0m"
\ No newline at end of file
diff --git a/includes.mk b/includes.mk
index 5bc8456..4634e38 100644
--- a/includes.mk
+++ b/includes.mk
@@ -1,6 +1,6 @@
-check-docker:
-	@if [ -z $$(which docker) ]; then \
-		echo "Missing \`docker\` client which is required for development"; \
+check-podman:
+	@if [ -z $$(which podman) ]; then \
+		echo "Missing \`podman\` client which is required for development"; \
 		exit 2; \
 	fi
 
diff --git a/main.go b/main.go
index 407a145..27b534c 100644
--- a/main.go
+++ b/main.go
@@ -1,56 +1,80 @@
 package main
 
 import (
-	"fmt"
-	"io/ioutil"
 	"log"
+	"net"
+	"net/url"
 	"os"
 	"os/exec"
+	"strings"
 )
 
 const (
-	registryBinary  = "/bin/registry"
-	registryConfig  = "/etc/docker/registry/config.yml"
-	minioHostEnvVar = "DRYCC_MINIO_SERVICE_HOST"
-	minioPortEnvVar = "DRYCC_MINIO_SERVICE_PORT"
-	command         = "serve"
+	registryBinary         = "/opt/drycc/registry/bin/registry"
+	registryHtpasswd       = "/opt/drycc/registry/etc/htpasswd"
+	registryConfigEnvVar   = "DRYCC_REGISTRY_CONFIG"
+	registryRedirectEnvVar = "DRYCC_REGISTRY_REDIRECT"
+	storageBucketEnvVar    = "DRYCC_STORAGE_BUCKET"
+	storageEndpointEnvVar  = "DRYCC_STORAGE_ENDPOINT"
+	storageAccesskeyEnvVar = "DRYCC_STORAGE_ACCESSKEY"
+	storageSecretkeyEnvVar = "DRYCC_STORAGE_SECRETKEY"
+	storagePathStyleEnvVar = "DRYCC_STORAGE_PATH_STYLE"
+	defaultCommand         = "serve"
 )
 
 func main() {
 	log.Println("INFO: Starting registry...")
-	mHost := os.Getenv(minioHostEnvVar)
-	mPort := os.Getenv(minioPortEnvVar)
 	os.Setenv("REGISTRY_STORAGE", "s3")
-	os.Setenv("REGISTRY_STORAGE_S3_BACKEND", "minio")
-	os.Setenv("REGISTRY_STORAGE_S3_REGIONENDPOINT", fmt.Sprintf("http://%s:%s", mHost, mPort))
+	mEndpoint := os.Getenv(storageEndpointEnvVar)
+	os.Setenv("REGISTRY_STORAGE_S3_REGIONENDPOINT", mEndpoint)
 
-	if accesskey, err := ioutil.ReadFile("/var/run/secrets/drycc/objectstore/creds/accesskey"); err != nil {
-		log.Fatal(err)
-	} else {
-		os.Setenv("REGISTRY_STORAGE_S3_ACCESSKEY", string(accesskey))
+	region := "us-east-1" //region is required in distribution
+	if endpointURL, err := url.Parse(mEndpoint); err == nil {
+		if endpointURL.Hostname() != "" && net.ParseIP(endpointURL.Hostname()) == nil {
+			region = strings.Split(endpointURL.Hostname(), ".")[0]
+		}
 	}
+	os.Setenv("REGISTRY_STORAGE_S3_REGION", region)
 
-	if secretkey, err := ioutil.ReadFile("/var/run/secrets/drycc/objectstore/creds/secretkey"); err != nil {
-		log.Fatal(err)
-	} else {
-		os.Setenv("REGISTRY_STORAGE_S3_SECRETKEY", string(secretkey))
+	os.Setenv("REGISTRY_STORAGE_S3_ACCESSKEY", os.Getenv(storageAccesskeyEnvVar))
+	os.Setenv("REGISTRY_STORAGE_S3_SECRETKEY", os.Getenv(storageSecretkeyEnvVar))
+	os.Setenv("REGISTRY_STORAGE_S3_BUCKET", os.Getenv(storageBucketEnvVar))
+
+	if os.Getenv(storagePathStyleEnvVar) == "true" {
+		os.Setenv("REGISTRY_STORAGE_S3_FORCEPATHSTYLE", "true")
 	}
-	if bucket, err := ioutil.ReadFile("/var/run/secrets/drycc/objectstore/creds/registry-bucket"); err != nil {
-		log.Fatal(err)
+
+	if os.Getenv(registryRedirectEnvVar) == "true" {
+		os.Setenv("REGISTRY_STORAGE_REDIRECT_DISABLE", "false")
 	} else {
-		os.Setenv("REGISTRY_STORAGE_S3_BUCKET", string(bucket))
+		os.Setenv("REGISTRY_STORAGE_REDIRECT_DISABLE", "true")
 	}
-	os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-east-1")
+	// set default env
+	os.Setenv("REGISTRY_STORAGE_S3_V4AUTH", "true")
+	os.Setenv("REGISTRY_STORAGE_S3_SECURE", "false")
+	os.Setenv("REGISTRY_STORAGE_S3_SKIPVERIFY", "true")
+	os.Setenv("REGISTRY_STORAGE_DELETE_ENABLED", "true")
+	os.Setenv("REGISTRY_VALIDATION_DISABLED", "true")
+	os.Setenv("REGISTRY_STORAGE_S3_ROOTDIRECTORY", "/registry")
 
-	// run /bin/create_bucket
-	cmd := exec.Command("/bin/create_bucket")
+	// run /bin/init-registry
+	os.Setenv("REGISTRY_AUTH", "htpasswd")
+	os.Setenv("REGISTRY_AUTH_HTPASSWD_REALM", "basic-realm")
+	os.Setenv("REGISTRY_AUTH_HTPASSWD_PATH", registryHtpasswd)
+	cmd := exec.Command("/bin/init-registry")
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 	if err := cmd.Run(); err != nil {
 		log.Fatal("Error creating the registry bucket: ", err)
 	}
+	// avoid conflicts with env variables
+	os.Unsetenv("REGISTRY_VERSION")
+	if len(os.Args) > 1 {
+		cmd = exec.Command(registryBinary, os.Args[1:]...)
+	} else {
+		cmd = exec.Command(registryBinary, defaultCommand, os.Getenv(registryConfigEnvVar))
+	}
 
-	cmd = exec.Command(registryBinary, command, registryConfig)
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 	if err := cmd.Run(); err != nil {
@@ -58,11 +82,3 @@ func main() {
 	}
 	log.Println("INFO: registry started.")
 }
-
-func getenv(name, dfault string) string {
-	value := os.Getenv(name)
-	if value == "" {
-		value = dfault
-	}
-	return value
-}
diff --git a/rootfs/bin/create_bucket b/rootfs/bin/create_bucket
deleted file mode 100755
index 196a744..0000000
--- a/rootfs/bin/create_bucket
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-# shellcheck disable=SC1091
-source /bin/normalize_storage
-
-has_bucket(){
-    mc ls minio -json|jq -r '.key'|grep -w "${MINIO_BUCKET}"
-}
-
-if  [ -z "$(has_bucket)" ] ;then
-    mc mb minio/"${MINIO_BUCKET}"
-    if  [ -z "$(has_bucket)" ] ;then
-        echo "create bucket ${MINIO_BUCKET} error"
-        exit 1
-    fi
-fi
-echo "create bucket ${MINIO_BUCKET} success"
diff --git a/rootfs/bin/init-registry b/rootfs/bin/init-registry
new file mode 100755
index 0000000..4d05fc8
--- /dev/null
+++ b/rootfs/bin/init-registry
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+set -e
+
+
+mkdir -p ~/.config/rclone
+touch ~/.config/rclone/rclone.conf
+rclone config create storage s3 \
+  provider=Other \
+  access_key_id="${DRYCC_STORAGE_ACCESSKEY}" \
+  secret_access_key="${DRYCC_STORAGE_SECRETKEY}" \
+  endpoint="${DRYCC_STORAGE_ENDPOINT}" \
+  force_path_style="${DRYCC_STORAGE_PATH_STYLE:-true}" --no-output
+
+if ! rclone lsd storage: > /dev/null 2>&1; then
+  sleep 9s
+  echo "waiting for object storage to become ready..."
+fi
+
+rclone mkdir "storage:${DRYCC_STORAGE_BUCKET}"
+
+htpasswd -Bbn "${DRYCC_REGISTRY_USERNAME}" "${DRYCC_REGISTRY_PASSWORD}" > "${REGISTRY_AUTH_HTPASSWD_PATH}"
+echo "create ${REGISTRY_AUTH_HTPASSWD_PATH} success"
diff --git a/rootfs/bin/normalize_storage b/rootfs/bin/normalize_storage
deleted file mode 100755
index fef58d8..0000000
--- a/rootfs/bin/normalize_storage
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env sh
-BUCKET_FILE="/var/run/secrets/drycc/objectstore/creds/registry-bucket"
-ACCESS_KEY_FILE="/var/run/secrets/drycc/objectstore/creds/accesskey"
-SECRET_KEY_FILE="/var/run/secrets/drycc/objectstore/creds/secretkey"
-
-if [ -f $BUCKET_FILE ]; then
-  MINIO_BUCKET=$(cat "$BUCKET_FILE")
-  export MINIO_BUCKET
-fi
-if [ -f $ACCESS_KEY_FILE ]; then
-  MINIO_ACCESS_KEY=$(cat "$ACCESS_KEY_FILE")
-  export MINIO_ACCESS_KEY
-fi
-if [ -f $SECRET_KEY_FILE ]; then
-  MINIO_SECRET_KEY=$(cat "$SECRET_KEY_FILE")
-  export MINIO_SECRET_KEY
-fi
-
-export MINIO_ENDPOINT=http://"${DRYCC_MINIO_SERVICE_HOST}:${DRYCC_MINIO_SERVICE_PORT}"
-
-mc config host add minio "${MINIO_ENDPOINT}" "${MINIO_ACCESS_KEY}" "${MINIO_SECRET_KEY}"
diff --git a/rootfs/bin/start-proxy b/rootfs/bin/start-proxy
new file mode 100755
index 0000000..5354551
--- /dev/null
+++ b/rootfs/bin/start-proxy
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+REGISTRY_HOST=${DRYCC_REGISTRY_HOST:?no host}
+AUTHORIZATION=$(echo -ne "${DRYCC_REGISTRY_USERNAME:? no username}":"${DRYCC_REGISTRY_PASSWORD:? no password}" | base64 -w 0)
+
+cat /opt/drycc/nginx/conf/registry.conf.tpl > /opt/drycc/nginx/conf/registry.conf
+sed -i "s#%REGISTRY_HOST%#${REGISTRY_HOST}#g" /opt/drycc/nginx/conf/registry.conf
+sed -i "s#%AUTHORIZATION%#${AUTHORIZATION}#g" /opt/drycc/nginx/conf/registry.conf
+
+# wait for registry to come online
+while ! curl -sS "$REGISTRY_HOST" &>/dev/null; do
+	echo "waiting for the registry (%s) to come online..."
+	echo "$REGISTRY_HOST"
+	sleep 1
+done
+
+echo "starting registry-proxy..."
+exec nginx -g "daemon off;"
diff --git a/rootfs/config-example.yml b/rootfs/config-example.yml
new file mode 100644
index 0000000..a55566c
--- /dev/null
+++ b/rootfs/config-example.yml
@@ -0,0 +1,23 @@
+version: 0.1
+log:
+  fields:
+    service: registry
+storage:
+  cache:
+    blobdescriptor: inmemory
+  filesystem:
+    rootdirectory: /var/lib/registry
+http:
+  addr: :5000
+  debug:
+    addr: :9000
+    prometheus:
+      enabled: true
+      path: /metrics
+  headers:
+    X-Content-Type-Options: [nosniff]
+health:
+  storagedriver:
+    enabled: true
+    interval: 10s
+    threshold: 3
diff --git a/rootfs/etc/docker/registry/config.yml b/rootfs/etc/docker/registry/config.yml
deleted file mode 100644
index b5700e1..0000000
--- a/rootfs/etc/docker/registry/config.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-version: 0.1
-log:
-  fields:
-    service: registry
-storage:
-    cache:
-        blobdescriptor: inmemory
-    filesystem:
-        rootdirectory: /var/lib/registry
-http:
-    addr: :5000
-    headers:
-        X-Content-Type-Options: [nosniff]
-health:
-  storagedriver:
-    enabled: true
-    interval: 10s
-    threshold: 3
diff --git a/rootfs/opt/drycc/nginx/conf/nginx.conf b/rootfs/opt/drycc/nginx/conf/nginx.conf
new file mode 100644
index 0000000..bd3f31d
--- /dev/null
+++ b/rootfs/opt/drycc/nginx/conf/nginx.conf
@@ -0,0 +1,22 @@
+worker_processes 1;
+
+error_log   /dev/stderr    warn;
+pid         /opt/drycc/nginx/logs/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include      /opt/drycc/nginx/conf/mime.types;
+    default_type application/octet-stream;
+
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+
+    access_log  /dev/stdout main;
+    sendfile on;
+    keepalive_timeout 65;
+    include /opt/drycc/nginx/conf/registry.conf;
+}
diff --git a/rootfs/opt/drycc/nginx/conf/registry.conf.tpl b/rootfs/opt/drycc/nginx/conf/registry.conf.tpl
new file mode 100644
index 0000000..627f2f2
--- /dev/null
+++ b/rootfs/opt/drycc/nginx/conf/registry.conf.tpl
@@ -0,0 +1,24 @@
+upstream container-registry {
+    server %REGISTRY_HOST%;
+}
+
+server {
+    listen 8080;
+    server_name localhost;
+    # disable any limits to avoid HTTP 413 for large image uploads
+    client_max_body_size 0;
+    # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
+    chunked_transfer_encoding on;
+    location / {
+        proxy_pass                          http://container-registry;
+        proxy_set_header  Host              $http_host;   # required for container client's sake
+        proxy_set_header  X-Real-IP         $remote_addr; # pass on real client's IP
+        proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
+        proxy_set_header  X-Forwarded-Proto $scheme;
+        proxy_read_timeout                  900;
+        proxy_set_header  Authorization     "Basic %AUTHORIZATION%";
+        limit_except      GET HEAD OPTIONS  {
+            deny all;
+        }
+    }
+}
\ No newline at end of file
diff --git a/versioning.mk b/versioning.mk
index 2bfd999..8b30958 100644
--- a/versioning.mk
+++ b/versioning.mk
@@ -11,13 +11,13 @@ info:
 	@echo "Immutable tag:   ${IMAGE}"
 	@echo "Mutable tag:     ${MUTABLE_IMAGE}"
 
-.PHONY: docker-push
-docker-push: docker-immutable-push docker-mutable-push
+.PHONY: podman-push
+podman-push: podman-immutable-push podman-mutable-push
 
-.PHONY: docker-immutable-push
-docker-immutable-push:
-	docker push ${IMAGE}
+.PHONY: podman-immutable-push
+podman-immutable-push:
+	podman push ${IMAGE}
 
-.PHONY: docker-mutable-push
-docker-mutable-push:
-	docker push ${MUTABLE_IMAGE}
+.PHONY: podman-mutable-push
+podman-mutable-push:
+	podman push ${MUTABLE_IMAGE}
