Skip to content

Commit ce50b94

Browse files
committed
Fix command/entrypoint mapping to match Docker semantics
command was mapped to ContainerSpec.Command (ENTRYPOINT) instead of ContainerSpec.Args (CMD). This broke images with entrypoints like cloudflare/cloudflared where command: ["tunnel", "run", ...] would try to execute "tunnel" as a binary instead of passing it as args to the cloudflared entrypoint.
1 parent c4a7853 commit ce50b94

3 files changed

Lines changed: 63 additions & 1 deletion

File tree

examples/swarm-app.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ service_specs:
2121
# {% endif %}
2222

2323
nginx:
24+
entrypoint: ["/docker-entrypoint.sh"]
2425
command: ["nginx", "-g", "daemon off;"]
2526
image: {{ env.NGINX_IMAGE_REF }}
2627
container_labels:

src/service-spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export function initServiceSpec ({appName, serviceName, config, hashedConfigs, c
7272
ContainerSpec: {
7373
Image: serviceConfig.image,
7474
Labels: serviceConfig.container_labels,
75-
Command: serviceConfig.command,
75+
Command: serviceConfig.entrypoint,
76+
Args: serviceConfig.command,
7677
Env: env,
7778
StopSignal: serviceConfig.stop_signal,
7879
StopGracePeriod: serviceConfig.stop_grace_period,

tests/service-spec.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {test, expect} from "@jest/globals";
2+
import {initServiceSpec} from "../src/service-spec.js";
3+
import {HashedConfigs} from "../src/hashed-config.js";
4+
import {SwarmAppConfig} from "../src/swarm-app-config.js";
5+
import {assertTaskTemplateContainerTaskSpec} from "../src/asserts.js";
6+
7+
test("command maps to Args and entrypoint maps to Command", () => {
8+
const config: SwarmAppConfig = {
9+
networks: {
10+
default: {name: "test-network", external: true},
11+
},
12+
service_specs: {
13+
server: {
14+
image: "cloudflare/cloudflared:2026.3.0",
15+
entrypoint: ["cloudflared", "--no-autoupdate"],
16+
command: ["tunnel", "run", "some-uuid"],
17+
service_labels: {"com.docker.stack.namespace": "test"},
18+
container_labels: {"com.docker.stack.namespace": "test"},
19+
},
20+
},
21+
};
22+
23+
const spec = initServiceSpec({
24+
appName: "test",
25+
serviceName: "server",
26+
config,
27+
hashedConfigs: new HashedConfigs(),
28+
});
29+
30+
assertTaskTemplateContainerTaskSpec(spec);
31+
expect(spec.TaskTemplate.ContainerSpec.Command).toEqual(["cloudflared", "--no-autoupdate"]);
32+
expect(spec.TaskTemplate.ContainerSpec.Args).toEqual(["tunnel", "run", "some-uuid"]);
33+
});
34+
35+
test("command without entrypoint sets Args only", () => {
36+
const config: SwarmAppConfig = {
37+
networks: {
38+
default: {name: "test-network", external: true},
39+
},
40+
service_specs: {
41+
server: {
42+
image: "nginx:latest",
43+
command: ["nginx", "-g", "daemon off;"],
44+
service_labels: {"com.docker.stack.namespace": "test"},
45+
container_labels: {"com.docker.stack.namespace": "test"},
46+
},
47+
},
48+
};
49+
50+
const spec = initServiceSpec({
51+
appName: "test",
52+
serviceName: "server",
53+
config,
54+
hashedConfigs: new HashedConfigs(),
55+
});
56+
57+
assertTaskTemplateContainerTaskSpec(spec);
58+
expect(spec.TaskTemplate.ContainerSpec.Command).toBeUndefined();
59+
expect(spec.TaskTemplate.ContainerSpec.Args).toEqual(["nginx", "-g", "daemon off;"]);
60+
});

0 commit comments

Comments
 (0)