Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions kubernetes/docker-registry/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v2
annotations:
category: registry
name: docker-registry
description: Docker registry
engine: gotpl

dependencies:
- name: common
repository: https://svtechnmaa.github.io/charts/artifacthub/
version: 1.x.x

version: 1.0.0
appVersion: "v1.0.0"
442 changes: 442 additions & 0 deletions kubernetes/docker-registry/README.md

Large diffs are not rendered by default.

Binary file not shown.
10 changes: 10 additions & 0 deletions kubernetes/docker-registry/templates/_hepers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{/*
SeaweedFS S3 endpoint
*/}}
{{- define "registry.s3.endpoint" -}}
{{- $protocol := .Values.seaweedfs.protocol | default "http" -}}
{{- $svc := .Values.seaweedfs.serviceName | default "seaweedfs-s3" -}}
{{- $ns := .Values.seaweedfs.namespace | default .Release.Namespace -}}
{{- $port := .Values.seaweedfs.port | default 8333 -}}
{{- printf "%s://%s.%s.svc.cluster.local:%v" $protocol $svc $ns $port -}}
{{- end }}
118 changes: 118 additions & 0 deletions kubernetes/docker-registry/templates/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-configuration" (include "common.names.fullname" .) }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "common.labels.standard" . | nindent 4 }}
app.kubernetes.io/component: docker-registry
data:
config.yml: |
# Docker Distribution Registry configuration
# Docs: https://distribution.github.io/distribution/about/configuration/
version: 0.1
log:
# Log level: error | warn | info | debug
level: {{ .Values.registry.log.level | default "info" }}
storage:
cache:
# Cache blob metadata (digest, size, mediatype) in memory
# → Reduces HEAD requests to SeaweedFS S3 when skopeo checks if blob already exists
# → Does NOT cache blob content, only metadata
# ⚠ If running multiple registry replicas, cache is NOT shared between them
blobdescriptor: inmemory
{{- $endpoint := .Values.registry.s3.endpointOverride | default (include "registry.s3.endpoint" .) }}
s3:
# Name of the bucket on SeaweedFS where all registry data is stored
# Bucket must exist before registry starts
bucket: {{ .Values.registry.s3.bucket | required "registry.s3.bucket is required" }}

# S3 access key configured in SeaweedFS s3 identity config
accesskey: {{ .Values.registry.s3.accesskey | required "registry.s3.accesskey is required" }}

# S3 secret key configured in SeaweedFS s3 identity config
secretkey: {{ .Values.registry.s3.secretkey | required "registry.s3.secretkey is required" }}

# AWS region — required by AWS SDK even when not using real AWS
# SeaweedFS ignores this value, any string is accepted
region: {{ .Values.registry.s3.region | default "us-east-1" }}

# Override default AWS S3 endpoint to point at SeaweedFS S3
# Required when using any S3-compatible service instead of real AWS
regionendpoint: {{ $endpoint }}

# Use HTTP instead of HTTPS when talking to SeaweedFS
# Set to true if SeaweedFS is configured with TLS
secure: {{ .Values.registry.s3.secure | default false }}

# Use AWS Signature Version 4 to sign all S3 requests
# SeaweedFS requires v4 — setting this to false will cause auth errors
v4auth: {{ .Values.registry.s3.v4auth | default true }}

# Root path prefix inside the bucket where all registry files are stored
# All data will be written to: s3://registry/<rootdirectory>/docker/registry/v2/...
# ⚠ Changing this after data exists will make existing images invisible to registry
rootdirectory: {{ .Values.registry.s3.rootdirectory | default "/" }}

# Force path-style S3 URLs instead of virtual-hosted-style
# Virtual-hosted-style (forcepathstyle: false — default):
# http://registry.seaweedfs:8333/docker/registry/v2/...
# ↑ bucket name embedded in hostname → SeaweedFS cannot resolve
#
# Path-style (forcepathstyle: true):
# http://seaweedfs:8333/registry/docker/registry/v2/...
# ↑ bucket name in URL path → SeaweedFS resolves correctly
#
# ⚠ REQUIRED when using regionendpoint with SeaweedFS
# Without this → error: "Copy Source must mention the source bucket and key"
forcepathstyle: {{ .Values.registry.s3.forcepathstyle | default true }}

# Size of each chunk when uploading a blob to S3 via multipart upload
# Default is 10MB → too many PATCH requests → increases chance of session loss
# 128MB → fewer PATCH requests per blob → more stable with SeaweedFS
# Minimum allowed by S3 spec: 5MB
# Formula: blob 256MB / chunksize 128MB = 2 PATCH requests total
chunksize: {{ .Values.registry.s3.chunksize | default 134217728 | int64 }}

# Blobs SMALLER than this value use a single S3 CopyObject call to finalize
# Blobs LARGER than this value use S3 Multipart Copy (multiple CopyObject calls)
# Default is 32MB → almost all blobs trigger Multipart Copy → hits SeaweedFS CopyObject bug
# 1GB → most image layers (which are typically < 1GB) use single copy → avoids the bug
# Error prevented: "NoSuchUpload" and "Copy Source must mention source bucket and key"
multipartcopythresholdsize: {{ .Values.registry.s3.multipartcopythresholdsize | default 1073741824 | int64 }}

# Size of each part when performing a Multipart Copy on SeaweedFS
# Only applies to blobs larger than multipartcopythresholdsize (> 1GB)
# Set equal to chunksize for consistency
multipartcopyChunksize: {{ .Values.registry.s3.multipartcopyChunksize | default 134217728 | int64 }}

# Maximum number of concurrent Multipart Copy operations
# Default is 100 → 100 parallel CopyObject calls → SeaweedFS race condition
# 1 → serial execution → slower but stable with SeaweedFS
# Only affects blobs larger than multipartcopythresholdsize (> 1GB)
multipartcopyMaxConcurrency: {{ .Values.registry.s3.multipartcopyMaxConcurrency | default 1 | int }}

# Disable S3 pre-signed URL redirect for blob downloads
# When false (redirect enabled): registry returns a pre-signed S3 URL
# → client downloads blob directly from SeaweedFS
# → works only if the client can reach the S3 endpoint
# When true (redirect disabled): registry proxies the blob content itself
# → required when SeaweedFS is only accessible inside the cluster
redirect:
disable: {{ .Values.registry.storage.redirect.disable | default false }}
http:
# Address and port the registry listens on
# ":5000" = 0.0.0.0:5000 → all network interfaces
# Use "127.0.0.1:5000" to restrict to localhost only
addr: {{ .Values.registry.http.addr | default ":5000" }}

# Secret key used to encrypt and decrypt upload session state (_state parameter)
# The _state token encodes: { UUID, Offset, StartedAt } for each active upload
# ⚠ Must be identical across all registry replicas in HA setup
# → if replicas have different secrets, PATCH/PUT from one replica cannot decrypt _state created by another → upload fails
# ⚠ Do not change while uploads are in progress
# → existing _state tokens cannot be decrypted → active uploads fail
secret: {{ .Values.registry.http.secret | required "registry.http.secret is required" }}

headers:
X-Content-Type-Options: {{ .Values.registry.http.headers.XContentTypeOptions | default "[nosniff]" }}
17 changes: 17 additions & 0 deletions kubernetes/docker-registry/templates/image_pull_secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# templates/imagepullsecret.yaml
{{- if .Values.imagePullSecret.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "common.names.fullname" . }}-pull-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "common.labels.standard" . | nindent 4 }}
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-10"
helm.sh/hook-delete-policy: before-hook-creation
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ printf `{"auths":{"%s":{"username":"%s","password":"%s","auth":"%s"}}}` .Values.imagePullSecret.registry .Values.imagePullSecret.username .Values.imagePullSecret.password (printf "%s:%s" .Values.imagePullSecret.username .Values.imagePullSecret.password | b64enc) | b64enc }}
{{- end }}
58 changes: 58 additions & 0 deletions kubernetes/docker-registry/templates/job_create_bucket.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "common.names.fullname" . }}-create-bucket
namespace: {{ .Release.Namespace }}
labels:
{{- include "common.labels.standard" . | nindent 4 }}
app.kubernetes.io/component: create-bucket
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-5"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed
spec:
{{- if .Values.createBucket.activeDeadlineSeconds }}
activeDeadlineSeconds: {{ .Values.createBucket.activeDeadlineSeconds }}
{{- end }}
template:
metadata:
labels:
{{- include "common.labels.standard" . | nindent 8 }}
app.kubernetes.io/component: create-bucket
spec:
restartPolicy: OnFailure
{{- if .Values.imagePullSecret.enabled }}
imagePullSecrets:
- name: {{ include "common.names.fullname" . }}-pull-secret
{{- end }}
containers:
- name: create-bucket
image: {{ .Values.createBucket.image.registry }}/{{ .Values.createBucket.image.repository }}:{{ .Values.createBucket.image.tag }}
imagePullPolicy: {{ .Values.createBucket.image.pullPolicy }}
{{- with .Values.createBucket.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- $endpoint := .Values.registry.s3.endpointOverride | default (include "registry.s3.endpoint" .) }}
env:
- name: S3_ENDPOINT
value: {{ $endpoint | quote }}
- name: S3_BUCKET
value: {{ .Values.registry.s3.bucket | quote }}
- name: S3_ACCESS_KEY
value: {{ .Values.registry.s3.accesskey | quote }}
- name: S3_SECRET_KEY
value: {{ .Values.registry.s3.secretkey | quote }}
command:
- /bin/sh
- -c
- |
set -e

echo "Setting up mc alias..."
mc alias set seaweedfs "${S3_ENDPOINT}" "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}"

echo "Creating bucket ${S3_BUCKET} if not exists..."
mc mb --ignore-existing "seaweedfs/${S3_BUCKET}"

echo "Done!"
91 changes: 91 additions & 0 deletions kubernetes/docker-registry/templates/registry_daemonset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{ include "common.names.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "common.labels.standard" . | nindent 4 }}
app.kubernetes.io/component: docker-registry
spec:
selector:
matchLabels:
{{- include "common.labels.matchLabels" . | nindent 6 }}
app.kubernetes.io/component: docker-registry
template:
metadata:
labels:
{{- include "common.labels.standard" . | nindent 8 }}
app.kubernetes.io/component: docker-registry
{{- with .Values.registry.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.registry.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- if .Values.imagePullSecret.enabled }}
imagePullSecrets:
- name: {{ include "common.names.fullname" . }}-pull-secret
{{- end }}
containers:
- name: registry
image: {{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }}
imagePullPolicy: {{ .Values.image.pullPolicy }}

ports:
- name: http
containerPort: 5000
protocol: TCP

{{- with .Values.registry.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}

{{- if .Values.registry.livenessProbe.enabled }}
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.registry.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.registry.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.registry.livenessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.registry.livenessProbe.failureThreshold }}
{{- end }}

{{- if .Values.registry.readinessProbe.enabled }}
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.registry.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.registry.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.registry.readinessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.registry.readinessProbe.failureThreshold }}
{{- end }}

volumeMounts:
- name: config
mountPath: /etc/docker/registry
readOnly: true

volumes:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: config
configMap:
name: {{ printf "%s-configuration" (include "common.names.fullname" .) }}

{{- with .Values.registry.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}

{{- with .Values.registry.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

{{- with .Values.registry.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
16 changes: 16 additions & 0 deletions kubernetes/docker-registry/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "common.names.fullname" . }}-service
namespace: {{ .Release.Namespace }}
spec:
selector:
{{- include "common.labels.matchLabels" . | nindent 4 }}
app.kubernetes.io/component: docker-registry
ports:
- name: http
port: 5000
targetPort: 5000
type: ClusterIP

Loading
Loading