Docker compose struggling with permission setting up Hashicorp Vault

I’m struggling while setting up Hashicorp Vault on Docker using Docker Compose in a dedicated Debian 13.3 VM.
Docker engine version is 29.1.4, docker compose is 2.26.1-4 , Vault image is 1.21.

By default, Vault image is using uid:gid 100:100, however this one is already existing on my Debian VM. To avoid any conflict, I created a vault-system user and group where uid:gid are 1001:1001.

At the moment, here’s how I configured permissions on my project folder. All folders are empty except tls, which contains certificate and private key :

root@my-server:/opt/vault-infra# ll . && ll tls/
total 20K
drwxr-x--- 2 vault-system vault-system 4,0K 16 janv. 09:48 config
drwx------ 2 vault-system vault-system 4,0K 16 janv. 09:23 data
-rw-r--r-- 1 vault-system vault-system  547 16 janv. 09:50 docker-compose.yml
drwxr-x--- 2 vault-system vault-system 4,0K 13 janv. 17:44 logs
drwx------ 2 vault-system vault-system 4,0K 15 janv. 15:37 tls
total 8,0K
-rw-r--r-- 1 vault-system vault-system 2,7K 15 janv. 14:22 tls.crt
-rw------- 1 vault-system vault-system 3,2K 15 janv. 14:22 tls.key
  • Test 1 : Setup using a simple Docker Compose file and configuration - the default user is the one in defined in official vault image so uid:gid is 100:100 by default.
#docker-compose.yml : 

services:
  vault:
    image: hashicorp/vault:1.21
    cap_add:
      - IPC_LOCK
    volumes:
      - /opt/vault-infra/tls:/vault/tls:ro
      - /opt/vault-infra/data:/vault/data
      - /opt/vault-infra/config:/vault/config
    environment:
      VAULT_LOCAL_CONFIG: |
        listener "tcp" {
          address       = "0.0.0.0:8200"
          tls_cert_file = "/vault/tls/tls.crt"
          tls_key_file  = "/vault/tls/tls.key"
        }
        storage "file" {
          path = "/vault/data"
        }
    command: server

#################################
root@my-server:/opt/vault-infra# docker compose up -d
WARN[0000] No services to build
[+] up 1/1
 ✔ Container vault-infra-vault-1 Recreated                                                                                                                                                                0.2s 
root@my-server:/opt/vault-infra# docker logs vault-infra-vault-1

WARNING! Unable to read storage migration status.
2026-01-16T09:23:37.346Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
2026-01-16T09:23:37.346Z [WARN]  storage migration check error: error="open /vault/data/core/_migration: permission denied"

=> Test 1 result: Vault can’t be set up as there’s a permission denied which is expected as uid:gid is the default one.
Also at this moment, I noticed that config folder seems to have been changed to uid 100 (which is dhcpcd in my VM) and local.json config has been created.

root@my-server:/opt/vault-infra# ls -l
total 20
drwxr-x--- 2 dhcpcd               1000 4096 16 janv. 10:23 config
drwx------ 2 vault-system vault-system 4096 16 janv. 09:23 data
-rw-r--r-- 1 vault-system vault-system  526 16 janv. 10:23 docker-compose.yml
drwxr-x--- 2 vault-system vault-system 4096 13 janv. 17:44 logs
drwx------ 2 vault-system vault-system 4096 15 janv. 15:37 tls

root@my-server:/opt/vault-infra# tree
./
├── config/
│   └── local.json
├── data/
├── logs/
├── tls/
│   ├── tls.crt
│   └── tls.key
└── docker-compose.yml
  • Test 2 : Setup using the same Docker Compose file but user’s uid:gid is updated to 1001:1001.
#docker-compose.yml : 
services:
  vault:
    image: hashicorp/vault:1.21
    user: "1001:1001"
    cap_add:
      - IPC_LOCK
    volumes:
      - /opt/vault-infra/tls:/vault/tls:ro
      - /opt/vault-infra/data:/vault/data
      - /opt/vault-infra/config:/vault/config
    environment:
      VAULT_LOCAL_CONFIG: |
        listener "tcp" {
          address       = "0.0.0.0:8200"
          tls_cert_file = "/vault/tls/tls.crt"
          tls_key_file  = "/vault/tls/tls.key"
        }
        storage "file" {
          path = "/vault/data"
        }
    command: server

##################################
root@my-server:/opt/vault-infra# docker compose up -d
WARN[0000] No services to build
[+] up 2/2
 ✔ Network vault-infra_default   Created                                                                                                                                                                  0.0s 
 ✔ Container vault-infra-vault-1 Created                                                                                                                                                                  0.1s 
root@my-server:/opt/vault-infra# docker logs vault-infra-vault-1
chown: /vault/config/local.json: Operation not permitted
chown: /vault/config: Operation not permitted
chown: /vault/config: Operation not permitted
Could not chown /vault/config (may not have appropriate permissions)
unable to set CAP_SETFCAP effective capability: Operation not permitted
root@my-server:/opt/vault-infra# ls -l
total 20
drwxr-x--- 2 vault-system vault-system 4096 16 janv. 10:26 config
drwx------ 2 vault-system vault-system 4096 16 janv. 09:23 data
-rw-r--r-- 1 vault-system vault-system  548 16 janv. 10:26 docker-compose.yml
drwxr-x--- 2 vault-system vault-system 4096 13 janv. 17:44 logs
drwx------ 2 vault-system vault-system 4096 15 janv. 15:37 tls
root@my-server:/opt/vault-infra# tree
./
├── config/
│   └── local.json
├── data/
├── logs/
├── tls/
│   ├── tls.crt
│   └── tls.key
└── docker-compose.yml

=> Test 2 result : We can see there’s still permissions issue, the image is kind of trying to set chown operations that failed, and local.json file has been created again.

  • Test 3 : Using group_add in docker compose, it means that default user (100:100) will be used, and will be add to group 1001
#docker-compose.yml : 
services:
  vault:
    image: hashicorp/vault:1.21
    group_add:
      - "1001"
    cap_add:
      - IPC_LOCK
    volumes:
      - /opt/vault-infra/tls:/vault/tls:ro
      - /opt/vault-infra/data:/vault/data
      - /opt/vault-infra/config:/vault/config
    environment:
      VAULT_LOCAL_CONFIG: |
        listener "tcp" {
          address       = "0.0.0.0:8200"
          tls_cert_file = "/vault/tls/tls.crt"
          tls_key_file  = "/vault/tls/tls.key"
        }
        storage "file" {
          path = "/vault/data"
        }
    command: server
##############################
root@my-server:/opt/vault-infra# docker compose up -d
WARN[0000] No services to build
[+] up 2/2
 ✔ Network vault-infra_default   Created                                                                                                                                                                  0.0s 
 ✔ Container vault-infra-vault-1 Created                                                                                                                                                                  0.1s 
root@my-server:/opt/vault-infra# docker logs vault-infra-vault-1

2026-01-16T09:28:48.624Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
2026-01-16T09:28:48.624Z [WARN]  storage migration check error: error="open /vault/data/core/_migration: permission denied"
WARNING! Unable to read storage migration status.
2026-01-16T09:28:50.624Z [WARN]  storage migration check error: error="open /vault/data/core/_migration: permission denied"
2026-01-16T09:28:52.624Z [WARN]  storage migration check error: error="open /vault/data/core/_migration: permission denied"
root@my-server:/opt/vault-infra# ls -l
total 20
drwxr-x--- 2 dhcpcd               1000 4096 16 janv. 10:28 config
drwx------ 2 vault-system vault-system 4096 16 janv. 09:23 data
-rw-r--r-- 1 vault-system vault-system  556 16 janv. 10:28 docker-compose.yml
drwxr-x--- 2 vault-system vault-system 4096 13 janv. 17:44 logs
drwx------ 2 vault-system vault-system 4096 15 janv. 15:37 tls
root@my-server:/opt/vault-infra# tree
./
├── config/
│   └── local.json
├── data/
├── logs/
├── tls/
│   ├── tls.crt
│   └── tls.key
└── docker-compose.yml

5 directories, 4 files

=> Test 3 result : config folder owner has changed to uid 100 (dhcpcd) , local.json has been created again and there’s permission issue on data folder.

  • Test 4 Set up my container using a one-liner Docker
root@my-server:/opt/vault-infra# docker run -it --rm --user 100:100 -v ./tls/:/vault/tls -v ./data/:/vault/data hashicorp/vault:1.21 sh
/ $ ls -l /vault/
total 20
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 config
drwx------    2 1001     1001          4096 Jan 16 08:23 data
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 file
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 logs
drwx------    2 1001     1001          4096 Jan 15 14:37 tls
/ $ ls -l /vault/tls/
ls: can't open '/vault/tls/': Permission denied
total 0
/ $ ls -l /vault/data
ls: can't open '/vault/data': Permission denied
total 0

=> Test 4 result : result is expected as user 100:100 don’t have any permissions on tls and data folders.

  • Test 5 : using a one-liner Docker and using uid:gid 1001:1001
root@my-server:/opt/vault-infra# docker run -it --rm --user 1001:1001 -v ./tls/:/vault/tls -v ./data/:/vault/data hashicorp/vault:1.21 sh
~ $ ls -l /vault/
total 20
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 config
drwx------    2 1001     1001          4096 Jan 16 08:23 data
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 file
drwxr-xr-x    2 vault    vault         4096 Jan  6 16:40 logs
drwx------    2 1001     1001          4096 Jan 15 14:37 tls
~ $ ls -l /vault/config/
total 0
~ $ ls -l /vault/data/
total 0
~ $ ls -l /vault/tls/
total 8
-rw-r--r--    1 1001     1001          2756 Jan 16 09:26 tls.crt
-rw-------    1 1001     1001          3272 Jan 16 09:26 tls.key
~ $ cat /vault/tls/tls.crt
-----BEGIN CERTIFICATE-----
MIIHxzCCBa+gAwIBAgITNwAAAGJx2Gx25yK6/gAAAAAAYjANBgkqhkiG9w0BAQsF
ADB4MQswCQYDVQQGEwJGUjEMMAoGA1UECBMDQWluMRgwFgYDVQQHEw9Cb3VyZyBl

=> Test 5 result : result is expected as UID:GID is updated => it works (note, it also works if GID only is updated)

My question is : how can I handle permissions and correct user in order to make Vault up without cutting security corners ?

This part of the entrypoint script is responsible for the behavior:
https://github.com/hashicorp/vault/blob/main/.release/docker/docker-entrypoint.sh#L74C4-L88C11

Seems there is an undocumented flag that might do the trick. Add this to your environment block:

SKIP_CHOWN: true

I suggest using it with user: "1001:1001" (make sure the folders/files are owned by the uid:gid).

If this is not working, then this image doesn’t seem to support what you are looking for.

1 Like

Sorry for my late answer, my account was on hold…
thanks for the cheats ! I finally change my strategy and use docker volumes instead of mount point, it succeed at the first try :slight_smile: