From 573b9c8ab06bb726a23fd11d0d406d6ddb13460f Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:08:01 -0600 Subject: [PATCH 1/5] docker-compose: Run services with a read-only root filesystem Set read_only: true on every service except prometheus, whose image writes its alertmanager config and data outside the mounted volumes. Writable paths follow the Helm chart's emptyDir mounts: /tmp for gitserver and searcher, /home/sourcegraph for the frontends, /var/run/postgresql for the databases. The frontends and blobstore also get /tmp. The two code intel workers get /tmp as a named volume, because their scratch files can be several GB and a tmpfs counts against mem_limit. Amp-Thread-ID: https://ampcode.com/threads/T-01a13cfb-9756-7111-8b29-381a841065e1 Co-authored-by: Amp --- docker-compose/docker-compose.yaml | 51 +++++++++++++++++++++++ docker-compose/jaeger/docker-compose.yaml | 1 + 2 files changed, 52 insertions(+) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index f0fb810ac..611c92688 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -17,6 +17,7 @@ services: mem_limit: '500m' security_opt: - no-new-privileges:true + read_only: true command: ['up'] environment: - PGHOST=pgsql @@ -75,6 +76,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true environment: - 'XDG_DATA_HOME=/caddy-storage/data' - 'XDG_CONFIG_HOME=/caddy-storage/config' @@ -132,6 +134,7 @@ services: mem_limit: '8g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - DEPLOY_TYPE=docker-compose @@ -166,6 +169,9 @@ services: start_period: 300s volumes: - 'sourcegraph-frontend-0:/mnt/cache' + tmpfs: + - '/home/sourcegraph' + - '/tmp' networks: - sourcegraph restart: always @@ -186,6 +192,7 @@ services: mem_limit: '8g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - DEPLOY_TYPE=docker-compose @@ -203,6 +210,9 @@ services: - 'OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317' volumes: - 'sourcegraph-frontend-internal-0:/mnt/cache' + tmpfs: + - '/home/sourcegraph' + - '/tmp' networks: - sourcegraph restart: always @@ -233,12 +243,15 @@ services: mem_limit: '8g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - 'SRC_FRONTEND_INTERNAL=sourcegraph-frontend-internal:3090' - 'OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317' volumes: - 'gitserver-0:/data/repos' + tmpfs: + - '/tmp' networks: - sourcegraph restart: always @@ -259,6 +272,7 @@ services: mem_limit: '16g' security_opt: - no-new-privileges:true + read_only: true environment: - 'HOSTNAME=zoekt-webserver-0:6070' - 'SRC_FRONTEND_INTERNAL=http://sourcegraph-frontend-internal:3090' @@ -284,6 +298,7 @@ services: mem_limit: '50g' security_opt: - no-new-privileges:true + read_only: true environment: - 'HOSTNAME=zoekt-webserver-0:6070' - 'OPENTELEMETRY_DISABLED=false' @@ -313,6 +328,7 @@ services: mem_limit: '2g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - 'SRC_FRONTEND_INTERNAL=sourcegraph-frontend-internal:3090' @@ -324,6 +340,8 @@ services: retries: 3 volumes: - 'searcher-0:/mnt/cache' + tmpfs: + - '/tmp' networks: - sourcegraph restart: always @@ -340,6 +358,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - 'SRC_FRONTEND_INTERNAL=sourcegraph-frontend-internal:3090' @@ -350,6 +369,9 @@ services: timeout: 5s retries: 3 start_period: 60s + volumes: + # On disk, not tmpfs: a tmpfs counts against mem_limit, and uploads can be several GB + - 'precise-code-intel-worker-tmp:/tmp' networks: - sourcegraph restart: always @@ -367,6 +389,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - 'SRC_FRONTEND_INTERNAL=sourcegraph-frontend-internal:3090' @@ -393,6 +416,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true env_file: sourcegraph-uploads.env environment: - 'SRC_FRONTEND_INTERNAL=sourcegraph-frontend-internal:3090' @@ -403,6 +427,9 @@ services: timeout: 15s retries: 3 start_period: 60s + volumes: + # On disk, not tmpfs: a tmpfs counts against mem_limit, and repository archives can be several GB + - 'syntactic-code-intel-worker-tmp:/tmp' networks: - sourcegraph restart: always @@ -420,6 +447,7 @@ services: mem_limit: '6g' security_opt: - no-new-privileges:true + read_only: true healthcheck: test: "wget -q 'http://127.0.0.1:9238/health' -O /dev/null || exit 1" interval: 5s @@ -473,6 +501,7 @@ services: mem_limit: '1g' security_opt: - no-new-privileges:true + read_only: true volumes: - 'grafana:/var/lib/grafana' - '../grafana/datasources:/sg_config_grafana/provisioning/datasources' @@ -494,6 +523,7 @@ services: mem_limit: '1g' security_opt: - no-new-privileges:true + read_only: true # You may set `privileged` to `false and `cadvisor` will run with reduced privileges. # `cadvisor` requires root privileges in order to display provisioning metrics. # These metrics provide critical information to help you scale the Sourcegraph deployment. @@ -529,6 +559,7 @@ services: mem_limit: '1g' security_opt: - no-new-privileges:true + read_only: true pid: 'host' volumes: - /proc:/host/proc:ro @@ -560,6 +591,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true shm_size: '1g' healthcheck: test: '/liveness.sh' @@ -570,6 +602,8 @@ services: volumes: - 'pgsql:/data/' - '../pgsql/conf:/conf/' + tmpfs: + - '/var/run/postgresql' networks: - sourcegraph restart: always @@ -590,6 +624,7 @@ services: mem_limit: '50m' security_opt: - no-new-privileges:true + read_only: true networks: - sourcegraph restart: always @@ -611,6 +646,7 @@ services: mem_limit: '4g' security_opt: - no-new-privileges:true + read_only: true shm_size: '1g' healthcheck: test: '/liveness.sh' @@ -621,6 +657,8 @@ services: volumes: - 'codeintel-db:/data/' - '../codeintel-db/conf:/conf/' + tmpfs: + - '/var/run/postgresql' networks: - sourcegraph restart: always @@ -641,6 +679,7 @@ services: mem_limit: '50m' security_opt: - no-new-privileges:true + read_only: true networks: - sourcegraph restart: always @@ -662,6 +701,7 @@ services: mem_limit: '2g' security_opt: - no-new-privileges:true + read_only: true shm_size: '1g' environment: - POSTGRES_PASSWORD=password @@ -677,6 +717,8 @@ services: volumes: - 'codeinsights-db:/var/lib/postgresql/data/' - '../codeinsights-db/conf:/conf/' + tmpfs: + - '/var/run/postgresql' networks: - sourcegraph restart: always @@ -697,6 +739,7 @@ services: mem_limit: '50m' security_opt: - no-new-privileges:true + read_only: true networks: - sourcegraph restart: always @@ -718,6 +761,7 @@ services: mem_limit: '1g' security_opt: - no-new-privileges:true + read_only: true healthcheck: test: ['CMD', 'curl', '-f', 'http://127.0.0.1:9000/'] interval: 10s @@ -726,6 +770,8 @@ services: start_period: 15s volumes: - 'blobstore:/data' + tmpfs: + - '/tmp' networks: - sourcegraph restart: always @@ -743,6 +789,7 @@ services: mem_limit: '7g' security_opt: - no-new-privileges:true + read_only: true volumes: - 'redis-cache:/redis-data' networks: @@ -761,6 +808,7 @@ services: mem_limit: '7g' security_opt: - no-new-privileges:true + read_only: true volumes: - 'redis-store:/redis-data' networks: @@ -778,6 +826,7 @@ services: mem_limit: '1g' security_opt: - no-new-privileges:true + read_only: true networks: - sourcegraph restart: always @@ -794,6 +843,7 @@ volumes: codeintel-db: codeinsights-db: blobstore: + precise-code-intel-worker-tmp: prometheus-v2: redis-cache: redis-store: @@ -801,6 +851,7 @@ volumes: searcher-0: sourcegraph-frontend-0: sourcegraph-frontend-internal-0: + syntactic-code-intel-worker-tmp: zoekt-0-shared: networks: sourcegraph: diff --git a/docker-compose/jaeger/docker-compose.yaml b/docker-compose/jaeger/docker-compose.yaml index a74593689..160355640 100644 --- a/docker-compose/jaeger/docker-compose.yaml +++ b/docker-compose/jaeger/docker-compose.yaml @@ -14,6 +14,7 @@ services: mem_limit: '512m' security_opt: - no-new-privileges:true + read_only: true ports: # Query port - '0.0.0.0:16686:16686' From 2cdd9006583f82f787c223e119156b012f755d4a Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:22:45 -0600 Subject: [PATCH 2/5] docker-compose: Make tmpfs mounts writable by the service user A tmpfs mounted over an existing directory keeps that directory's mode but is owned by root, so postgres could not create its socket lock file in /var/run/postgresql and the smoke test failed. Set uid and gid on the /var/run/postgresql and /home/sourcegraph mounts to the image's user. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a13cfb-9756-7111-8b29-381a841065e1 --- docker-compose/docker-compose.yaml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 611c92688..8b803c68f 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -170,7 +170,8 @@ services: volumes: - 'sourcegraph-frontend-0:/mnt/cache' tmpfs: - - '/home/sourcegraph' + # Owned by the image's sourcegraph user; a tmpfs is owned by root by default + - '/home/sourcegraph:uid=100,gid=101' - '/tmp' networks: - sourcegraph @@ -211,7 +212,8 @@ services: volumes: - 'sourcegraph-frontend-internal-0:/mnt/cache' tmpfs: - - '/home/sourcegraph' + # Owned by the image's sourcegraph user; a tmpfs is owned by root by default + - '/home/sourcegraph:uid=100,gid=101' - '/tmp' networks: - sourcegraph @@ -603,7 +605,8 @@ services: - 'pgsql:/data/' - '../pgsql/conf:/conf/' tmpfs: - - '/var/run/postgresql' + # Owned by the image's postgres user; a tmpfs is owned by root by default + - '/var/run/postgresql:uid=999,gid=999' networks: - sourcegraph restart: always @@ -658,7 +661,8 @@ services: - 'codeintel-db:/data/' - '../codeintel-db/conf:/conf/' tmpfs: - - '/var/run/postgresql' + # Owned by the image's postgres user; a tmpfs is owned by root by default + - '/var/run/postgresql:uid=999,gid=999' networks: - sourcegraph restart: always @@ -718,7 +722,8 @@ services: - 'codeinsights-db:/var/lib/postgresql/data/' - '../codeinsights-db/conf:/conf/' tmpfs: - - '/var/run/postgresql' + # Owned by the image's postgres user; a tmpfs is owned by root by default + - '/var/run/postgresql:uid=999,gid=999' networks: - sourcegraph restart: always From 32ede2fbe4968e50f9dc3180c3f9b284cbf6fac7 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:36:36 -0600 Subject: [PATCH 3/5] docker-compose: Use mode=1777 for tmpfs mounts instead of uid and gid The postgres user is uid 999 in the postgresql-16 image but uid 70 in the codeinsights image, so codeinsights-db still could not write its socket lock file. A world-writable sticky tmpfs, like /tmp, works for any user. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a13cfb-9756-7111-8b29-381a841065e1 --- docker-compose/docker-compose.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 8b803c68f..a4d478419 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -170,8 +170,8 @@ services: volumes: - 'sourcegraph-frontend-0:/mnt/cache' tmpfs: - # Owned by the image's sourcegraph user; a tmpfs is owned by root by default - - '/home/sourcegraph:uid=100,gid=101' + # mode=1777: a tmpfs is owned by root, not by the image's user + - '/home/sourcegraph:mode=1777' - '/tmp' networks: - sourcegraph @@ -212,8 +212,8 @@ services: volumes: - 'sourcegraph-frontend-internal-0:/mnt/cache' tmpfs: - # Owned by the image's sourcegraph user; a tmpfs is owned by root by default - - '/home/sourcegraph:uid=100,gid=101' + # mode=1777: a tmpfs is owned by root, not by the image's user + - '/home/sourcegraph:mode=1777' - '/tmp' networks: - sourcegraph @@ -605,8 +605,8 @@ services: - 'pgsql:/data/' - '../pgsql/conf:/conf/' tmpfs: - # Owned by the image's postgres user; a tmpfs is owned by root by default - - '/var/run/postgresql:uid=999,gid=999' + # mode=1777: a tmpfs is owned by root, not by the image's user + - '/var/run/postgresql:mode=1777' networks: - sourcegraph restart: always @@ -661,8 +661,8 @@ services: - 'codeintel-db:/data/' - '../codeintel-db/conf:/conf/' tmpfs: - # Owned by the image's postgres user; a tmpfs is owned by root by default - - '/var/run/postgresql:uid=999,gid=999' + # mode=1777: a tmpfs is owned by root, not by the image's user + - '/var/run/postgresql:mode=1777' networks: - sourcegraph restart: always @@ -722,8 +722,8 @@ services: - 'codeinsights-db:/var/lib/postgresql/data/' - '../codeinsights-db/conf:/conf/' tmpfs: - # Owned by the image's postgres user; a tmpfs is owned by root by default - - '/var/run/postgresql:uid=999,gid=999' + # mode=1777: a tmpfs is owned by root, not by the image's user + - '/var/run/postgresql:mode=1777' networks: - sourcegraph restart: always From fd841c15280e80fcb39590a3b59fd25a2b9c5b6b Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:36:37 -0600 Subject: [PATCH 4/5] docker-compose: Run the executor with a read-only root filesystem The executor writes workspaces under TMPDIR, which is the /scratch bind mount. The git, docker, and src commands it runs do not inherit TMPDIR, so /tmp is a tmpfs. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a13cfb-9756-7111-8b29-381a841065e1 --- docker-compose/executors/executor.docker-compose.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose/executors/executor.docker-compose.yaml b/docker-compose/executors/executor.docker-compose.yaml index 473bc7072..7fa2f1ad2 100644 --- a/docker-compose/executors/executor.docker-compose.yaml +++ b/docker-compose/executors/executor.docker-compose.yaml @@ -8,6 +8,7 @@ services: user: root # Run with privileged capabilities (required for docker daemon control) privileged: true + read_only: true environment: # Refer to https://sourcegraph.com/docs/self-hosted/executors/executors-config on how to populate these variables - EXECUTOR_FRONTEND_URL=http://sourcegraph-frontend-0:3080 @@ -27,6 +28,9 @@ services: - '/var/run/docker.sock:/var/run/docker.sock' # Mount volume for workspaces shared by executor and launched containers - '/scratch:/scratch' + tmpfs: + # The git, docker, and src commands the executor runs do not inherit TMPDIR + - '/tmp' networks: - sourcegraph restart: always From 9db1af331d82a0ad5b16fba7efd896761b3f1473 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:53:31 -0600 Subject: [PATCH 5/5] docker-compose: Use volumes instead of tmpfs for postgres and frontend CI showed mode=1777 has no effect on a tmpfs mounted over an existing image directory: postgres still could not write to /var/run/postgresql. A named volume takes the owner and mode of the image directory it covers, so it is writable by the image's user without naming a uid, gid, or mode in the compose file. /tmp stays a tmpfs: it is 1777 in the images. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a1d36e-7d38-77c8-98dc-e4678a3ad2b0 --- .../db-only-migrate.docker-compose.yaml | 3 ++ docker-compose/docker-compose.yaml | 28 ++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docker-compose/db-only-migrate.docker-compose.yaml b/docker-compose/db-only-migrate.docker-compose.yaml index cb991c3fa..0dce0f78a 100644 --- a/docker-compose/db-only-migrate.docker-compose.yaml +++ b/docker-compose/db-only-migrate.docker-compose.yaml @@ -23,7 +23,10 @@ services: volumes: pgsql: + pgsql-socket: codeintel-db: + codeintel-db-socket: codeinsights-db: + codeinsights-db-socket: networks: sourcegraph: diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index a4d478419..13fb188bf 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -169,9 +169,9 @@ services: start_period: 300s volumes: - 'sourcegraph-frontend-0:/mnt/cache' + # A volume, not a tmpfs: a volume takes the image directory's owner, a tmpfs is owned by root + - 'sourcegraph-frontend-0-home:/home/sourcegraph' tmpfs: - # mode=1777: a tmpfs is owned by root, not by the image's user - - '/home/sourcegraph:mode=1777' - '/tmp' networks: - sourcegraph @@ -211,9 +211,9 @@ services: - 'OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317' volumes: - 'sourcegraph-frontend-internal-0:/mnt/cache' + # A volume, not a tmpfs: a volume takes the image directory's owner, a tmpfs is owned by root + - 'sourcegraph-frontend-internal-0-home:/home/sourcegraph' tmpfs: - # mode=1777: a tmpfs is owned by root, not by the image's user - - '/home/sourcegraph:mode=1777' - '/tmp' networks: - sourcegraph @@ -604,9 +604,8 @@ services: volumes: - 'pgsql:/data/' - '../pgsql/conf:/conf/' - tmpfs: - # mode=1777: a tmpfs is owned by root, not by the image's user - - '/var/run/postgresql:mode=1777' + # A volume, not a tmpfs: a volume takes the image directory's owner, a tmpfs is owned by root + - 'pgsql-socket:/var/run/postgresql' networks: - sourcegraph restart: always @@ -660,9 +659,8 @@ services: volumes: - 'codeintel-db:/data/' - '../codeintel-db/conf:/conf/' - tmpfs: - # mode=1777: a tmpfs is owned by root, not by the image's user - - '/var/run/postgresql:mode=1777' + # A volume, not a tmpfs: a volume takes the image directory's owner, a tmpfs is owned by root + - 'codeintel-db-socket:/var/run/postgresql' networks: - sourcegraph restart: always @@ -721,9 +719,8 @@ services: volumes: - 'codeinsights-db:/var/lib/postgresql/data/' - '../codeinsights-db/conf:/conf/' - tmpfs: - # mode=1777: a tmpfs is owned by root, not by the image's user - - '/var/run/postgresql:mode=1777' + # A volume, not a tmpfs: a volume takes the image directory's owner, a tmpfs is owned by root + - 'codeinsights-db-socket:/var/run/postgresql' networks: - sourcegraph restart: always @@ -845,8 +842,11 @@ volumes: gitserver-0: grafana: pgsql: + pgsql-socket: codeintel-db: + codeintel-db-socket: codeinsights-db: + codeinsights-db-socket: blobstore: precise-code-intel-worker-tmp: prometheus-v2: @@ -855,7 +855,9 @@ volumes: worker: searcher-0: sourcegraph-frontend-0: + sourcegraph-frontend-0-home: sourcegraph-frontend-internal-0: + sourcegraph-frontend-internal-0-home: syntactic-code-intel-worker-tmp: zoekt-0-shared: networks: