From 562001dc0b94ae05191e552eaf9811ae1bd7e3a5 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Sun, 27 Sep 2026 18:09:43 +0200 Subject: [PATCH] fix(deployment): run plugin container as the reposilite user The reposilite entrypoint chowns /app to 977:977 whenever it starts as root. The plugin container downloaded the jar as its own image user (101), so after the first start the file was owned by 977:977 with mode 0644. Since the plugins emptyDir survives a container restart within the same pod, a restarted plugin container could no longer overwrite the existing jar and aborted with "Permission denied", which left the pod in a permanent Init:CrashLoopBackOff until the pod itself was recreated. Run the plugin container as 977:977 by default and expose the security context as deployment.pluginContainer.securityContext, so it can be aligned when PUID/PGID are overridden. Co-authored-by: Copilot --- README.md | 84 +++++++++++++------------ templates/_deployment.tpl | 6 +- unittests/deployment/configPlugins.yaml | 3 + values.yaml | 9 +++ 4 files changed, 60 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 0be0d0d..cd081d0 100644 --- a/README.md +++ b/README.md @@ -270,47 +270,49 @@ spec: ### Deployment -| Name | Description | Value | -| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `deployment.annotations` | Additional deployment annotations. | `{}` | -| `deployment.labels` | Additional deployment labels. | `{}` | -| `deployment.additionalContainers` | List of additional containers. | `[]` | -| `deployment.affinity` | Affinity for the Reposilite deployment. | `{}` | -| `deployment.initContainers` | List of additional init containers. | `[]` | -| `deployment.dnsConfig` | dnsConfig of the Reposilite deployment. | `{}` | -| `deployment.dnsPolicy` | dnsPolicy of the Reposilite deployment. | `""` | -| `deployment.hostname` | Individual hostname of the pod. | `""` | -| `deployment.subdomain` | Individual domain of the pod. | `""` | -| `deployment.hostNetwork` | Use the kernel network namespace of the host system. | `false` | -| `deployment.imagePullSecrets` | Secret to use for pulling the image. | `[]` | -| `deployment.reposilite.args` | Arguments passed to the Reposilite container. | `[]` | -| `deployment.reposilite.command` | Command passed to the Reposilite container. | `[]` | -| `deployment.reposilite.env` | List of environment variables for the Reposilite container. | | -| `deployment.reposilite.envFrom` | List of environment variables mounted from configMaps or secrets for the Reposilite container. | `[]` | -| `deployment.reposilite.image.registry` | Image registry, eg. `docker.io`. | `docker.io` | -| `deployment.reposilite.image.repository` | Image repository, eg. `library/busybox`. | `dzikoysk/reposilite` | -| `deployment.reposilite.image.tag` | Custom image tag, eg. `0.1.0`. Defaults to `appVersion`. | `""` | -| `deployment.reposilite.image.pullPolicy` | Image pull policy. | `IfNotPresent` | -| `deployment.reposilite.resources` | CPU and memory resources of the pod. | `{}` | -| `deployment.reposilite.securityContext` | Security context of the container of the deployment. | `{}` | -| `deployment.reposilite.volumeMounts` | Additional volume mounts. | `[]` | -| `deployment.nodeSelector` | NodeSelector of the Reposilite deployment. | `{}` | -| `deployment.pluginContainer.args` | Arguments passed to the plugin container. | `["--location","--fail","--max-time","60"]` | -| `deployment.pluginContainer.image.registry` | Image registry, eg. `docker.io`. | `docker.io` | -| `deployment.pluginContainer.image.repository` | Image repository, eg. `curlimages/curl`. | `curlimages/curl` | -| `deployment.pluginContainer.image.tag` | Custom image tag, eg. `0.1.0`. | `8.22.0` | -| `deployment.pluginContainer.image.pullPolicy` | Image pull policy. | `IfNotPresent` | -| `deployment.priorityClassName` | PriorityClassName of the Reposilite deployment. | `""` | -| `deployment.replicas` | Number of replicas for the Reposilite deployment. | `1` | -| `deployment.restartPolicy` | Restart policy of the Reposilite deployment. | `""` | -| `deployment.securityContext` | Security context of the Reposilite deployment. | `{}` | -| `deployment.strategy.type` | Strategy type - `Recreate` or `RollingUpdate`. | `RollingUpdate` | -| `deployment.strategy.rollingUpdate.maxSurge` | The maximum number of pods that can be scheduled above the desired number of pods during a rolling update. | `1` | -| `deployment.strategy.rollingUpdate.maxUnavailable` | The maximum number of pods that can be unavailable during a rolling update. | `1` | -| `deployment.terminationGracePeriodSeconds` | How long to wait until forcefully kill the pod. | `60` | -| `deployment.tolerations` | Tolerations of the Reposilite deployment. | `[]` | -| `deployment.topologySpreadConstraints` | TopologySpreadConstraints of the Reposilite deployment. | `[]` | -| `deployment.volumes` | Additional volumes to mount into the pods of the reposilite deployment. | `[]` | +| Name | Description | Value | +| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `deployment.annotations` | Additional deployment annotations. | `{}` | +| `deployment.labels` | Additional deployment labels. | `{}` | +| `deployment.additionalContainers` | List of additional containers. | `[]` | +| `deployment.affinity` | Affinity for the Reposilite deployment. | `{}` | +| `deployment.initContainers` | List of additional init containers. | `[]` | +| `deployment.dnsConfig` | dnsConfig of the Reposilite deployment. | `{}` | +| `deployment.dnsPolicy` | dnsPolicy of the Reposilite deployment. | `""` | +| `deployment.hostname` | Individual hostname of the pod. | `""` | +| `deployment.subdomain` | Individual domain of the pod. | `""` | +| `deployment.hostNetwork` | Use the kernel network namespace of the host system. | `false` | +| `deployment.imagePullSecrets` | Secret to use for pulling the image. | `[]` | +| `deployment.reposilite.args` | Arguments passed to the Reposilite container. | `[]` | +| `deployment.reposilite.command` | Command passed to the Reposilite container. | `[]` | +| `deployment.reposilite.env` | List of environment variables for the Reposilite container. | | +| `deployment.reposilite.envFrom` | List of environment variables mounted from configMaps or secrets for the Reposilite container. | `[]` | +| `deployment.reposilite.image.registry` | Image registry, eg. `docker.io`. | `docker.io` | +| `deployment.reposilite.image.repository` | Image repository, eg. `library/busybox`. | `dzikoysk/reposilite` | +| `deployment.reposilite.image.tag` | Custom image tag, eg. `0.1.0`. Defaults to `appVersion`. | `""` | +| `deployment.reposilite.image.pullPolicy` | Image pull policy. | `IfNotPresent` | +| `deployment.reposilite.resources` | CPU and memory resources of the pod. | `{}` | +| `deployment.reposilite.securityContext` | Security context of the container of the deployment. | `{}` | +| `deployment.reposilite.volumeMounts` | Additional volume mounts. | `[]` | +| `deployment.nodeSelector` | NodeSelector of the Reposilite deployment. | `{}` | +| `deployment.pluginContainer.args` | Arguments passed to the plugin container. | `["--location","--fail","--max-time","60"]` | +| `deployment.pluginContainer.image.registry` | Image registry, eg. `docker.io`. | `docker.io` | +| `deployment.pluginContainer.image.repository` | Image repository, eg. `curlimages/curl`. | `curlimages/curl` | +| `deployment.pluginContainer.image.tag` | Custom image tag, eg. `0.1.0`. | `8.22.0` | +| `deployment.pluginContainer.image.pullPolicy` | Image pull policy. | `IfNotPresent` | +| `deployment.pluginContainer.securityContext.runAsGroup` | Group id of the plugin container. | `977` | +| `deployment.pluginContainer.securityContext.runAsUser` | User id of the plugin container. | `977` | +| `deployment.priorityClassName` | PriorityClassName of the Reposilite deployment. | `""` | +| `deployment.replicas` | Number of replicas for the Reposilite deployment. | `1` | +| `deployment.restartPolicy` | Restart policy of the Reposilite deployment. | `""` | +| `deployment.securityContext` | Security context of the Reposilite deployment. | `{}` | +| `deployment.strategy.type` | Strategy type - `Recreate` or `RollingUpdate`. | `RollingUpdate` | +| `deployment.strategy.rollingUpdate.maxSurge` | The maximum number of pods that can be scheduled above the desired number of pods during a rolling update. | `1` | +| `deployment.strategy.rollingUpdate.maxUnavailable` | The maximum number of pods that can be unavailable during a rolling update. | `1` | +| `deployment.terminationGracePeriodSeconds` | How long to wait until forcefully kill the pod. | `60` | +| `deployment.tolerations` | Tolerations of the Reposilite deployment. | `[]` | +| `deployment.topologySpreadConstraints` | TopologySpreadConstraints of the Reposilite deployment. | `[]` | +| `deployment.volumes` | Additional volumes to mount into the pods of the reposilite deployment. | `[]` | ### Horizontal Pod Autoscaler (HPA) diff --git a/templates/_deployment.tpl b/templates/_deployment.tpl index 154377f..f6e7122 100644 --- a/templates/_deployment.tpl +++ b/templates/_deployment.tpl @@ -71,7 +71,11 @@ {{- if eq (include "reposilite.plugins.prometheus.enabled" $) "true" }} {{- $fileName := splitList "/" (tpl .Values.config.plugins.prometheus.url $) | last }} {{- $individualArgs := concat $pluginContainerArgs (list "--output" $fileName (tpl .Values.config.plugins.prometheus.url $)) }} -{{- $initContainers = concat $initContainers (list (dict "args" $individualArgs "name" "download-prometheus-plugin" "image" $pluginContainerImage "volumeMounts" $pluginContainerVolumeMounts)) }} +{{- $pluginContainer := dict "args" $individualArgs "name" "download-prometheus-plugin" "image" $pluginContainerImage "volumeMounts" $pluginContainerVolumeMounts }} +{{- with .Values.deployment.pluginContainer.securityContext }} +{{- $_ := set $pluginContainer "securityContext" . }} +{{- end }} +{{- $initContainers = concat $initContainers (list $pluginContainer) }} {{- end }} {{ toYaml (dict "initContainers" $initContainers) }} diff --git a/unittests/deployment/configPlugins.yaml b/unittests/deployment/configPlugins.yaml index f8ccdd3..7f51ad0 100644 --- a/unittests/deployment/configPlugins.yaml +++ b/unittests/deployment/configPlugins.yaml @@ -30,6 +30,9 @@ tests: - https://reposilite.com/plugins/prometheus.jar name: download-prometheus-plugin image: docker.io/curlimages/curl:0.1.0 + securityContext: + runAsGroup: 977 + runAsUser: 977 volumeMounts: - mountPath: /app/data/plugins name: plugins diff --git a/values.yaml b/values.yaml index 4b2c9eb..7d7c520 100644 --- a/values.yaml +++ b/values.yaml @@ -178,6 +178,15 @@ deployment: tag: "8.22.0" pullPolicy: IfNotPresent + ## @param deployment.pluginContainer.securityContext.runAsGroup Group id of the plugin container. + ## @param deployment.pluginContainer.securityContext.runAsUser User id of the plugin container. + # Reposilite chowns /app to 977:977 when its entrypoint starts as root. Downloading the plugin as a + # different user leaves behind a file the plugin container can no longer overwrite, which breaks every + # restart that reuses the emptyDir. Align this with PUID/PGID when those are overridden. + securityContext: + runAsGroup: 977 + runAsUser: 977 + ## @param deployment.priorityClassName PriorityClassName of the Reposilite deployment. priorityClassName: ""