docker service create looks like docker run with extra flags, until you realise it asks the whole cluster to keep tasks alive. This creates a two-replica Nginx service on a Swarm manager, confirms the tasks converge, checks the published port, and removes the test service cleanly. Docker Community CLI 29.8.1, packaged here as docker-ce-cli 5:29.8.1-1~ubuntu.24.04~noble.
Allow about fifteen minutes. You need Docker Engine access, an initialised Swarm, and a shell on a manager node. The examples create a real service and may pull an image, so use a test name and, on a shared cluster, a maintenance window. Docker commands normally need no sudo when your account can reach the socket; add it only if your local setup requires it.
Start with read-only checks. Neither creates a container nor changes cluster state:
$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker service create --help | sed -n '1,12p'
Usage: docker service create [OPTIONS] IMAGE [COMMAND] [ARG...]
Create a new service
$ docker info --format 'swarm={{.Swarm.LocalNodeState}} control={{.Swarm.ControlAvailable}}'
swarm=active control=true
The last line has to name an active manager. The installed help is explicit about the required shape: options, an image, then an optional command and its arguments. Fix a worker, an inactive Swarm, a stopped daemon or a socket permission error before you go further. Do not run docker swarm init on a machine meant to join an existing cluster: initialising a new Swarm is a separate, disruptive decision that does not belong in this guide.
Use the public nginx:alpine image, two replicated tasks, and a host port unlikely to be taken. The service name is what every later inspection and removal command will use to find it:
$ SERVICE_NAME='demo-web'
$ HOST_PORT='8080'
$ docker service create \
--name "$SERVICE_NAME" \
--replicas 2 \
--publish published="$HOST_PORT",target=80 \
nginx:alpine
This is the first state-changing command. It asks the manager to maintain two tasks continuously, not just start two containers once, in the default replicated mode. Port 8080 on every Swarm node now points at port 80 inside the service tasks. Docker prints a service ID, but keeping the name in a shell variable saves you copying that ID through the rest of this walkthrough.
Warning: a published port affects the whole cluster's routing mesh. Stop here if port 8080 is already allocated, or if an external firewall, load balancer or production service already uses it. Change HOST_PORT to an approved unused port before you create the service.
List the service by name and check the replica count:
$ docker service ls --filter "name=$SERVICE_NAME"
ID NAME MODE REPLICAS IMAGE PORTS
... demo-web replicated 2/2 nginx:alpine *:8080->80/tcp
The ID is shortened here and will differ on your cluster. 2/2 is the checkpoint that matters. A value like 0/2 means the manager has the desired state recorded but has not started both tasks yet: give an image pull or scheduling step time to settle, then inspect the tasks rather than recreating the service out of impatience.
$ docker service ps "$SERVICE_NAME" \
--format 'table {{.Name}}\t{{.Node}}\t{{.CurrentState}}\t{{.Error}}'
NAME NODE CURRENT STATE ERROR
demo-web.1 manager-1 Running 10 seconds ago
demo-web.2 worker-1 Running 10 seconds ago
Task names, nodes and times will vary. Both should reach Running. If a task keeps getting replaced, read the final column and the full task output: an unavailable image, a port conflict, no node satisfying a constraint, or a command that exits immediately are the usual causes. A service can exist quite happily while its tasks stay pending or failed.
Check the manager's recorded specification rather than trusting what you remember typing:
$ docker service inspect "$SERVICE_NAME" \
--format 'name={{.Spec.Name}} replicas={{.Spec.Mode.Replicated.Replicas}}'
name=demo-web replicas=2
$ docker service inspect "$SERVICE_NAME" \
--format '{{range .Endpoint.Ports}}{{.PublishedPort}}->{{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}'
8080->80/tcp
These read the service specification and endpoint, not one container's settings. The service uses the virtual IP endpoint mode by default; if you need DNS round-robin instead, create a separate test service with --endpoint-mode dnsrr rather than changing a live one just to check.
Test the published endpoint from a machine that can actually reach the node and port:
$ curl --fail --silent --show-error "http://127.0.0.1:$HOST_PORT/" \
| sed -n '1,3p'
<!DOCTYPE html>
<html>
The response body varies with the image version. A connection failure alone does not prove the service is unhealthy: check the node address, firewall, routing mesh settings and task states separately before you conclude anything.
Warning: removal is deliberate and irreversible for this service definition. It stops and deletes the tasks and removes the service record. Confirm the name before running it:
$ docker service ls --filter "name=$SERVICE_NAME"
ID NAME MODE REPLICAS IMAGE PORTS
... demo-web replicated 2/2 nginx:alpine *:8080->80/tcp
$ docker service rm "$SERVICE_NAME"
demo-web
$ docker service ls --filter "name=$SERVICE_NAME"
ID NAME MODE REPLICAS IMAGE PORTS
There is no undo for docker service rm. Recreate the test by rerunning the creation command and waiting for the image and tasks to converge. Removing the service does not touch unrelated images, named volumes, overlay networks or Swarm nodes: inspect and remove those separately if you need to.
alpine ping docker.com uses the Alpine filesystem and runs ping docker.com; put the command before the image and Docker usually errors out.--env KEY=value for ordinary configuration, --secret for sensitive values, --config for non-secret configuration files. Never put a password in a label, environment variable or a visible command example.--constraint restricts which nodes can run a task; --reserve-memory affects placement, --limit-memory constrains the task afterwards.--replicas-max-per-node defaults to unlimited, and the restart condition defaults to any with a five-second delay.--mode global for one task per node instead of a large replica count; --mode replicated-job or --mode global-job (with --max-concurrent for replicated jobs) for a finite operation with different update and completion behaviour.docker service ls shows the desired replica count.docker service ps shows both tasks running, or exposed a specific error.docker service inspect against the intended replicas and port mapping.