fix(compose): parse mode suffixes in long-form bind targets

This commit is contained in:
Andras Bacsai committed 2026-10-26 00:01:07 +01:00
1 parent cbe734c15b
commit 0d22b30910
6 files changed
+31 -6

No files matched your search

+1
View File
@@ -262,6 +262,7 @@ The persistent-storage implementation is the reference pattern: `Project\Service
- Run `vendor/bin/pint --dirty --format agent` before finalizing changes
- Every change must have tests — write or update tests, then run them. For bug fixes, follow TDD: write a failing test first, then fix the bug (see Test Enforcement below)
- Check sibling files for conventions before creating new files
- Nixpacks is deprecated. Never recommend it to users (issue replies, docs, UI copy, workarounds). Recommend the Dockerfile build pack, or Railpack where it fits.
- When adding remote shell commands, account for servers using non-root SSH users: commands pass through `parseCommandsByLineForSudo()`, so test pipelines, redirects, substitutions, and `sh -c`/`bash -c` scripts with the non-root sudo parser.
## Git Workflow
+6 -1
View File
@@ -148,7 +148,8 @@ function composeResourceDirectory(Application|Service $resource): string
* Renders a long-syntax bind with its resolved source. It keeps the options that Docker Compose
* accepts, such as read_only and bind, and removes other keys, which Compose rejects. Without
* `bind.create_host_path`, a missing source is created, as with short syntax: Docker Compose
* before v2.30 does not do that for long syntax.
* before v2.30 does not do that for long syntax. A short-syntax mode in the target
* (`/data/users.yml:ro`) becomes `read_only`, because Docker rejects it in long syntax.
*
* @param array<string, mixed> $volume
* @return array<string, mixed>
@@ -160,6 +161,10 @@ function composeLongBindVolume(array $volume, string $source): array
fn ($key) => in_array($key, ['type', 'source', 'target', 'read_only', 'consistency', 'bind'], true) || str_starts_with((string) $key, 'x-'),
ARRAY_FILTER_USE_KEY
);
if (preg_match('/^(.+):(ro|rw)$/', (string) ($volume['target'] ?? ''), $matches)) {
$volume['target'] = $matches[1];
$volume['read_only'] ??= $matches[2] === 'ro';
}
$volume['source'] = $source;
$volume['bind'] = is_array($volume['bind'] ?? null) ? $volume['bind'] : [];
$volume['bind']['create_host_path'] ??= true;
+1 -1
View File
@@ -15,7 +15,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
- type: bind
source: ./data/users.yml
target: /data/users.yml:ro
target: /data/users.yml
content: |
users:
# "admin" is the username
+2 -2
View File
@@ -1138,7 +1138,7 @@
"dozzle-with-auth": {
"documentation": "https://dozzle.dev/?utm_source=coolify.io",
"slogan": "Dozzle is a simple and lightweight web UI for Docker logs.",
"compose": "c2VydmljZXM6CiAgZG96emxlOgogICAgaW1hZ2U6ICdhbWlyMjAvZG96emxlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0RPWlpMRV84MDgwCiAgICAgIC0gRE9aWkxFX0FVVEhfUFJPVklERVI9c2ltcGxlCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZGF0YS91c2Vycy55bWwKICAgICAgICB0YXJnZXQ6ICcvZGF0YS91c2Vycy55bWw6cm8nCiAgICAgICAgY29udGVudDogInVzZXJzOlxuICAjIFwiYWRtaW5cIiBpcyB0aGUgdXNlcm5hbWVcbiAgYWRtaW46XG4gICAgZW1haWw6IHRlc3RAZW1haWwuY29tXG4gICAgbmFtZTogQWRtaW5cbiAgICAjIEEgYmNyeXB0IGhhc2ggb2YgdGhlIHBhc3N3b3JkIHlvdSB3YW50IHRvIHVzZS4gQ2FuIGJlIGNvbXB1dGVkIHdpdGggXCJweXRob24zIC1jICdpbXBvcnQgYmNyeXB0O3ByaW50KGJjcnlwdC5oYXNocHcoYlwieW91ci1wYXNzd29yZFwiLGJjcnlwdC5nZW5zYWx0KDExKSkuZGVjb2RlKCkpJyBcIi4gRGVmYXVsdCBwYXNzd29yZCBpcyBcInRlc3RcIi5cbiAgICBwYXNzd29yZDogJDJhJDExJHZpdWNDdkZMbEhXdkJOT09JNnV5cHVWVS5EMDlVV2IuenN3UnhFZzBNa0RQaTFxL2JLYmRHXG4iCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL2RvenpsZQogICAgICAgIC0gaGVhbHRoY2hlY2sKICAgICAgaW50ZXJ2YWw6IDNzCiAgICAgIHRpbWVvdXQ6IDMwcwogICAgICByZXRyaWVzOiA1Cg==",
"compose": "c2VydmljZXM6CiAgZG96emxlOgogICAgaW1hZ2U6ICdhbWlyMjAvZG96emxlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0RPWlpMRV84MDgwCiAgICAgIC0gRE9aWkxFX0FVVEhfUFJPVklERVI9c2ltcGxlCiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vZGF0YS91c2Vycy55bWwKICAgICAgICB0YXJnZXQ6IC9kYXRhL3VzZXJzLnltbAogICAgICAgIGNvbnRlbnQ6ICJ1c2VyczpcbiAgIyBcImFkbWluXCIgaXMgdGhlIHVzZXJuYW1lXG4gIGFkbWluOlxuICAgIGVtYWlsOiB0ZXN0QGVtYWlsLmNvbVxuICAgIG5hbWU6IEFkbWluXG4gICAgIyBBIGJjcnlwdCBoYXNoIG9mIHRoZSBwYXNzd29yZCB5b3Ugd2FudCB0byB1c2UuIENhbiBiZSBjb21wdXRlZCB3aXRoIFwicHl0aG9uMyAtYyAnaW1wb3J0IGJjcnlwdDtwcmludChiY3J5cHQuaGFzaHB3KGJcInlvdXItcGFzc3dvcmRcIixiY3J5cHQuZ2Vuc2FsdCgxMSkpLmRlY29kZSgpKScgXCIuIERlZmF1bHQgcGFzc3dvcmQgaXMgXCJ0ZXN0XCIuXG4gICAgcGFzc3dvcmQ6ICQyYSQxMSR2aXVjQ3ZGTGxIV3ZCTk9PSTZ1eXB1VlUuRDA5VVdiLnpzd1J4RWcwTWtEUGkxcS9iS2JkR1xuIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9kb3p6bGUKICAgICAgICAtIGhlYWx0aGNoZWNrCiAgICAgIGludGVydmFsOiAzcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogNQo=",
"tags": [
"dozzle",
"docker",
@@ -3509,7 +3509,7 @@
"category": "messaging",
"logo": "svgs/mosquitto.svg",
"minversion": "0.0.0",
"template_last_updated_at": "2026-01-05T18:25:15+01:00",
"template_last_updated_at": "2026-10-23T22:58:59+02:00",
"port": "1883"
},
"n8n-with-postgres-and-worker": {
+2 -2
View File
@@ -1138,7 +1138,7 @@
"dozzle-with-auth": {
"documentation": "https://dozzle.dev/?utm_source=coolify.io",
"slogan": "Dozzle is a simple and lightweight web UI for Docker logs.",
"compose": "c2VydmljZXM6CiAgZG96emxlOgogICAgaW1hZ2U6ICdhbWlyMjAvZG96emxlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ET1paTEVfODA4MAogICAgICAtIERPWlpMRV9BVVRIX1BST1ZJREVSPXNpbXBsZQogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2RhdGEvdXNlcnMueW1sCiAgICAgICAgdGFyZ2V0OiAnL2RhdGEvdXNlcnMueW1sOnJvJwogICAgICAgIGNvbnRlbnQ6ICJ1c2VyczpcbiAgIyBcImFkbWluXCIgaXMgdGhlIHVzZXJuYW1lXG4gIGFkbWluOlxuICAgIGVtYWlsOiB0ZXN0QGVtYWlsLmNvbVxuICAgIG5hbWU6IEFkbWluXG4gICAgIyBBIGJjcnlwdCBoYXNoIG9mIHRoZSBwYXNzd29yZCB5b3Ugd2FudCB0byB1c2UuIENhbiBiZSBjb21wdXRlZCB3aXRoIFwicHl0aG9uMyAtYyAnaW1wb3J0IGJjcnlwdDtwcmludChiY3J5cHQuaGFzaHB3KGJcInlvdXItcGFzc3dvcmRcIixiY3J5cHQuZ2Vuc2FsdCgxMSkpLmRlY29kZSgpKScgXCIuIERlZmF1bHQgcGFzc3dvcmQgaXMgXCJ0ZXN0XCIuXG4gICAgcGFzc3dvcmQ6ICQyYSQxMSR2aXVjQ3ZGTGxIV3ZCTk9PSTZ1eXB1VlUuRDA5VVdiLnpzd1J4RWcwTWtEUGkxcS9iS2JkR1xuIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9kb3p6bGUKICAgICAgICAtIGhlYWx0aGNoZWNrCiAgICAgIGludGVydmFsOiAzcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogNQo=",
"compose": "c2VydmljZXM6CiAgZG96emxlOgogICAgaW1hZ2U6ICdhbWlyMjAvZG96emxlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ET1paTEVfODA4MAogICAgICAtIERPWlpMRV9BVVRIX1BST1ZJREVSPXNpbXBsZQogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2RhdGEvdXNlcnMueW1sCiAgICAgICAgdGFyZ2V0OiAvZGF0YS91c2Vycy55bWwKICAgICAgICBjb250ZW50OiAidXNlcnM6XG4gICMgXCJhZG1pblwiIGlzIHRoZSB1c2VybmFtZVxuICBhZG1pbjpcbiAgICBlbWFpbDogdGVzdEBlbWFpbC5jb21cbiAgICBuYW1lOiBBZG1pblxuICAgICMgQSBiY3J5cHQgaGFzaCBvZiB0aGUgcGFzc3dvcmQgeW91IHdhbnQgdG8gdXNlLiBDYW4gYmUgY29tcHV0ZWQgd2l0aCBcInB5dGhvbjMgLWMgJ2ltcG9ydCBiY3J5cHQ7cHJpbnQoYmNyeXB0Lmhhc2hwdyhiXCJ5b3VyLXBhc3N3b3JkXCIsYmNyeXB0LmdlbnNhbHQoMTEpKS5kZWNvZGUoKSknIFwiLiBEZWZhdWx0IHBhc3N3b3JkIGlzIFwidGVzdFwiLlxuICAgIHBhc3N3b3JkOiAkMmEkMTEkdml1Y0N2RkxsSFd2Qk5PT0k2dXlwdVZVLkQwOVVXYi56c3dSeEVnME1rRFBpMXEvYktiZEdcbiIKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvZG96emxlCiAgICAgICAgLSBoZWFsdGhjaGVjawogICAgICBpbnRlcnZhbDogM3MKICAgICAgdGltZW91dDogMzBzCiAgICAgIHJldHJpZXM6IDUK",
"tags": [
"dozzle",
"docker",
@@ -3509,7 +3509,7 @@
"category": "messaging",
"logo": "svgs/mosquitto.svg",
"minversion": "0.0.0",
"template_last_updated_at": "2026-01-05T18:25:15+01:00",
"template_last_updated_at": "2026-10-23T22:58:59+02:00",
"port": "1883"
},
"n8n-with-postgres-and-worker": {
@@ -235,3 +235,22 @@ it('keeps a ${VAR:-default} long-form bind source for Docker Compose when the va
'variable reference' => ['$BASE_DIR/ao.yaml'],
'vault reference' => ['{{vault.AO_CONFIG_FILE}}'],
]);
it('moves a mode suffix from the target of a long-form bind to read_only', function (string $kind, string $mode, bool $readOnly) {
$result = parseLongBind($kind, <<<YAML
services:
probe:
image: alpine:3.22
volumes:
- type: bind
source: ./data/users.yml
target: /fixture:{$mode}
content: 'users: {}'
YAML);
expect($result['volume']['target'])->toBe('/fixture')
->and($result['volume']['read_only'])->toBe($readOnly);
})->with(['service', 'application'])->with([
'read-only' => ['ro', true],
'read-write' => ['rw', false],
]);