docker/compose · error
cannot create %s %q in read-only service %s: `file` is the s
Error message
cannot create %s %q in read-only service %s: `file` is the sole supported option
What it means
When starting a service, compose materializes secrets/configs that are inlined (content:), from environment, or CDK-provided by creating them inside the container's filesystem at the mount target. If the service is declared read_only: true, compose cannot write those files and requires the source to come from a host file (file:), which is bind-mounted read-only instead.
Source
Thrown at pkg/compose/secrets.go:59
func (s *composeService) injectConfigs(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error {
return s.injectFileReferences(ctx, project, service, id, configMount)
}
func (s *composeService) injectFileReferences(ctx context.Context, project *types.Project, service types.ServiceConfig, id string, mountType mountType) error {
mounts, sources := s.getFilesAndMap(project, service, mountType)
for _, mount := range mounts {
content, err := s.resolveFileContent(project, sources[mount.Source], mountType)
if err != nil {
return err
}
if content == "" {
continue
}
if service.ReadOnly {
return fmt.Errorf("cannot create %s %q in read-only service %s: `file` is the sole supported option", mountType, sources[mount.Source].Name, service.Name)
}
if mount.Target == "" {
if mountType == secretMount {
mount.Target = "/run/secrets/" + mount.Source
} else {
mount.Target = "/" + mount.Source
}
} else if mountType == secretMount && !isAbsTarget(mount.Target) {
mount.Target = "/run/secrets/" + mount.Target
}
if err := s.copyFileToContainer(ctx, id, content, mount); err != nil {
return err
}
}
return nil
}View on GitHub (pinned to ddc4b044b6)
Solutions
- Define the secret/config with file: pointing at a host path so it is mounted rather than written.
- Remove read_only: true (or scope it) if the service must use inline content secrets.
- Use an external secret driver that surfaces the secret as a file compatible with read-only roots.
- Ensure the file path exists and is readable by the daemon host user.
Example fix
# before
services:
app:
read_only: true
secrets: [api_token]
secrets:
api_token:
environment: API_TOKEN # must be written into container → error
# after
services:
app:
read_only: true
secrets: [api_token]
secrets:
api_token:
file: ./secrets/api_token.txt Defensive patterns
Strategy: validation
Validate before calling
func readOnlySafeMounts(project *types.Project) error {
for name, svc := range project.Services {
if !svc.ReadOnly {
continue
}
for _, sec := range svc.Secrets { checkFileSource(project.Secrets, sec, "secret", name) }
for _, cfg := range svc.Configs { checkFileSource(project.Configs, cfg, "config", name) }
}
return nil // checkFileSource errors when source uses content:/environment: without file:
} Try / catch
if err := compose.Up(ctx, project, opts); err != nil {
if strings.Contains(err.Error(), "read-only service") && strings.Contains(err.Error(), "sole supported option") {
// convert the named secret/config to file: or drop read_only
}
return err
} Prevention
- Pair read_only: true services only with file-based secrets/configs.
- Prefer file: sources in production compose files generally.
- Test up on a staging project before rolling hardened read-only settings out.
When it happens
Trigger: A service with read_only: true that mounts a secret or config defined via content:, via environment:, or otherwise resolved to inline content. The file-creation path requires a writable container filesystem, so compose errors naming the service and mount.
Common situations: Hardening a service by adding read_only: true without migrating its inline configs to files; using secrets: top-level entries with file: pointing at a real path works, but content: / env: variants do not.
Related errors
- failed to scan config file %s: %w
- failed to scan secret file %s: %w
- environment variable %q required by %s %q is not set
- no containers for project %q
- Docker Compose does not support configs.*.driver
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/ea19a0fb9322f79d.
Report an issue: GitHub.