An incident starts, and the tool for the job is docker service logs, aimed at the right service with a useful time window. Allow about ten minutes if you already know the service name. The examples use Docker CE CLI 29.8.1, installed here as docker-ce-cli version 5:29.8.1-1~ubuntu.24.04~noble.
This is an inspection workflow: it does not restart, redeploy or remove a service. Run the commands as an ordinary user first. You need access to the Docker socket, and the command must run on a Swarm manager. Do not reach for sudo just because a log lookup failed: membership of the Docker group is itself a security decision, because it can grant effective root access to the host.
Confirm which Docker client you are using and whether this node is a manager before spending time on filters:
$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker info --format 'Swarm={{.Swarm.LocalNodeState}} Manager={{.Swarm.ControlAvailable}}'
Swarm=active Manager=true
Your own version and state will differ. You need an active Swarm and a manager node: this is a Swarm management command, not a general-purpose way to read logs from an unrelated standalone container. If the output says Swarm=inactive or Manager=false, move to an approved manager before continuing. Enabling Swarm or changing node roles is outside this guide and can alter cluster state.
Checkpoint: you have a manager node and a Docker client that can reach its daemon.
List services so a copied name does not silently point you at the wrong workload:
$ docker service ls
ID NAME MODE REPLICAS IMAGE
SERVICE_ID SERVICE_NAME replicated 3/3 IMAGE:TAG
The headings and values above are examples. Copy the real value from the NAME column into SERVICE_NAME.
To see the tasks and their current states so you can pick one:
$ docker service ps SERVICE_NAME
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
TASK_ID SERVICE_NAME.1 IMAGE:TAG NODE_NAME Running Running ...
Replace both placeholders with real values. Keep the task ID handy if you need to isolate one replica. Do not guess a task ID from its slot number.
The default for --tail is all, which can flood your terminal and bury the current incident under history. Start small and add timestamps instead:
$ docker service logs --tail 100 --timestamps SERVICE_NAME
2026-09-23T10:14:02.123456789Z SERVICE_NAME.1....| application output
2026-09-23T10:14:03.987654321Z SERVICE_NAME.2....| application output
Docker adds an RFC3339-style timestamp; the task names, separators and application text are host-specific. What matters is that the command exits successfully and you can identify the time and replica for each line.
For a recent window, use --since. Relative durations are convenient mid-incident:
$ docker service logs --since 30m --tail 200 --timestamps SERVICE_NAME
You can also provide an RFC3339 timestamp such as 2026-09-23T09:45:00Z. Include an explicit timezone when comparing output from different machines: a bare timestamp is read in the client's local timezone, which is an easy way to end up chasing a log that was never missing.
Checkpoint: you have a manageable sample, a time boundary, and enough identity information to tell replicas apart.
If one task keeps restarting or returns different output, pass its ID instead of the service name:
$ docker service logs --tail 100 --timestamps TASK_ID
Run docker service ps SERVICE_NAME again if the task has since been replaced: a task ID from an older deployment may have no useful logs left. Comparing replicas? Run the same bounded command once per current task and keep the time window identical.
Use --follow when you need to watch new standard output and standard error as they happen:
$ docker service logs --follow --since 5m --timestamps SERVICE_NAME
2026-09-23T10:20:00.000000000Z SERVICE_NAME.1....| waiting for request
^C
Press Ctrl-C to stop following. That only ends your log client; it does not stop the service. Combining --follow with --since gives you a small historical lead-in before new lines arrive. Leave a follow command running only as long as you need it, because a busy service can fill your terminal fast.
Reach for these only when the default display is hiding something you need:
--no-trunc keeps longer log lines intact.--no-task-ids removes task IDs when you are building a compact, human-readable report.--no-resolve leaves IDs unmapped instead of resolving them to names.--raw disables Docker's neat formatting, which can help when another tool consumes the output.Warning: --details deserves extra care. It surfaces attributes supplied through logging options, which can include environment variables or labels. Treat its output as potentially sensitive: do not paste it into a public ticket or chat until you have stripped credentials, tokens and internal addresses.
First check the service name, manager status and task history. Then check the service's logging configuration:
$ docker service inspect --format '{{json .Spec.TaskTemplate.LogDriver}}' SERVICE_NAME
{"Name":"json-file","Options":{}}
The output depends on the service. Docker's command reference documents this log retrieval path for services using the json-file or journald logging driver: a different driver can explain why the service exists but this command returns nothing usable. Changing a logging driver is normally a deliberate service update, so do not alter it mid-diagnosis without a maintenance and retention plan.
If --tail is rejected, use a non-negative integer such as 100 or leave it at its default of all. If the daemon reports that the node is not a manager, reconnect to a manager rather than trying increasingly privileged local commands. If the application has logged nothing to standard output or standard error, this command cannot manufacture an entry: check the application's own logging configuration and destination instead.
--follow only for the live window and stopped it with Ctrl-C.--details output out of public tickets and changed no service or cluster state.