Nexus Shell

Remote Docker · macOS

Troubleshoot Remote Docker Containers from Your Mac over SSH

By the Nexus Shell team. Updated October 24, 2026. The configuration experiment below retains its September 10 verification date; the resource-reading examples use explicitly synthetic counters verified on October 24.

To investigate a Docker container on a remote Linux server, connect over SSH and check its state, recent logs and host resources before changing anything. Start with the macOS terminal if this is an occasional task. A server workspace becomes useful when you repeatedly move between container details, logs, terminal commands and remote files.

This guide covers a remote Docker Engine, not installing Docker Desktop on your Mac. We build Nexus Shell; the command-line workflow is a valid alternative, not a prerequisite to buying our app.

Before you connect

Use a non-production host for your first run. You need its hostname, SSH port, an account you are allowed to use, and permission to access Docker on that host. Docker access is powerful; a graphical client does not reduce the privileges of the remote account. Confirm the server's host-key fingerprint through a trusted channel.

Connect using your existing SSH configuration. Replace the example host and container name with your own test targets; the commands below run in the remote shell.


ssh your-test-host
hostname
docker version --format '{{.Server.Version}}'
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'

If Docker reports permission denied, resolve the intended access policy with the host administrator. Do not expose an unauthenticated Docker TCP port to make the connection work. Docker documents SSH as a way to reach a remote daemon without opening that port.

SSH lists containers, but the Mac Docker view is empty

Short answer: compare the SSH account and Docker endpoint before restarting anything. A terminal can target a different daemon from a graphical client, especially after sudo, a context switch, or a rootless setup. An empty list alone does not mean your containers were deleted.

Check the remote account and endpoint

Run these read-only checks in the remote SSH shell where your containers are visible:


hostname
id -un
command -v docker
docker context show
docker context ls
printenv DOCKER_HOST DOCKER_CONTEXT
docker info --format 'daemon={{.Name}} root={{.DockerRootDir}}'
docker ps -a --format 'table {{.Names}}\t{{.Status}}'

An unset variable produces no value; printenv may exit nonzero for missing variables. Keep the raw error if docker info fails. Review hostnames, paths and container names before sharing output.

Docker's context documentation explains endpoint selection and overrides. Its rootless instructions explain the user socket and remote SSH environment.

Compare your Mac terminal with the remote shell

If you also have a Docker CLI on the Mac, run docker context ls there separately. A Mac context pointing to Docker Desktop, Colima or another local runtime is not the Linux engine reached by SSH. Switching a CLI context does not guarantee that every graphical app follows it. For a one-command check of an existing, approved context, use docker --context CONTEXT_NAME ps -a rather than changing the global selection.

Nexus Shell's Docker view runs commands on the selected SSH connection. Verify that connection's account and server; do not assume it imports the Mac's Docker contexts or an interactive shell's exports. If your required engine cannot be reached by that account and command environment, validate compatibility before purchasing Pro. A successful SSH login is not a Docker compatibility test.

You do not need Docker Desktop on your Mac to run docker inside the remote SSH shell. The local CLI/context route is a separate option; Docker's SSH socket guidance requires permission to reach the remote socket. Do not expose an unauthenticated TCP daemon to bridge the difference.

1. Read the state before restarting

An exited container and a running-but-unhealthy service need different investigations. Inspect the exact container rather than restarting everything on the host.


docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} restarts={{.RestartCount}}' nexus-seo-docker-lab-20260910

An exit code alone is not a diagnosis. OOMKilled=false does not prove the server has enough resources; it only rules out that particular recorded container state. If health checks are configured, inspect their results too. A restart may erase useful timing clues without correcting a missing setting.

2. Read a bounded log sample


docker logs --since 10m --tail 100 nexus-seo-docker-lab-20260910

Use both a time range and a line limit. Logs may contain credentials or customer data, so inspect them before copying them into a support request or an AI conversation. If no logs appear, check the logging driver and whether the application writes to stdout/stderr or a file.

In our isolated example, the log identifies a missing application setting: APP_MODE is required. That supports checking the container's launch configuration. It does not justify changing the firewall, granting root access or deleting a volume.

3. Check host resources separately


df -h
free -h
docker stats --no-stream

These resource commands are for the Linux host. A stopped container may not appear in docker stats. A single healthy resource sample cannot rule out an earlier spike; combine it with the failure time and whatever historical monitoring you already have.

Container CPU above 100 percent: compare the denominator first

Short answer: Docker's Linux CPU percentage can exceed 100 because roughly 100% represents one CPU core, not the entire Mac or a container's quota. A container using 1.5 core-seconds per second can show about 150% in docker stats while using 75% of a two-core allocation. Neither number alone says the container is overloaded.

First identify what you connected to. An SSH session on the Linux Docker host monitors that host; its Docker view can inspect individual containers. An SSH session ending inside an application container has a different scope. A Docker Desktop daemon runs in a Linux VM on your Mac, so its CPU accounting is not macOS Activity Monitor accounting. Do not compare different daemons, timestamps or scopes as though they were one sample.

On the intended Docker host, replace your-container with the exact authorized target and take one bounded snapshot:


docker stats --no-stream your-container
docker inspect --format 'quota={{.HostConfig.CpuQuota}} period={{.HostConfig.CpuPeriod}} nanoCPUs={{.HostConfig.NanoCpus}} cpuset={{.HostConfig.CpusetCpus}} memoryBytes={{.HostConfig.Memory}}' your-container

These commands inspect existing state; they do not set a limit or restart a workload. Configured limits are clues, not proof of effective capacity: parent cgroups and cpusets can impose tighter restrictions. An absent quota is not a zero-core allocation. Do not mount the Docker socket or grant extra privileges just to obtain a chart.

Inside a container: host memory is not its allowance

Tools reading /proc can expose host-wide totals even when the shell runs inside a container. On a cgroup v2 system, inspect the current shell's mapping and readable counters before interpreting them:


cat /proc/self/cgroup
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpuset.cpus.effective
cat /sys/fs/cgroup/cpu.stat
cat /sys/fs/cgroup/cpu.pressure
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max

The root paths above apply only when that mount represents the target cgroup, as commonly happens with a private cgroup namespace. With a host namespace or nested layout, resolve the actual cgroup path first; cgroup v1 uses different files. Missing files or permission errors mean unavailable evidence, not zero usage. Run no privileged workaround. A cpu.max value of 200000 100000 describes a two-core bandwidth quota; max means no local quota, and parent limits can still apply. Likewise, memory.max=max does not promise unlimited physical memory.

Docker's Linux CLI subtracts inactive file cache from its memory display, while its API exposes raw usage and cache separately. For a synthetic memory.current of 512 MiB with 128 MiB inactive file cache, a cache-adjusted reading is 384 MiB. A raw 512 MiB chart and a 384 MiB chart can both be internally consistent. Neither is a prediction of whether the kernel will kill a process; inspect memory events and the workload's failure time separately.

CPU usage, pressure and throttling answer different questions

The reproducible resource-reading examples include offline source and recorded results for CPU denominators, pressure, period throttling, memory cache, missing limits and resets. The Chinese checklist covers the same workflow. These are arithmetic fixtures, not a live VPS benchmark or a Nexus Shell GUI compatibility test.

Check the reading in Nexus Shell before choosing Pro

Website / Homebrew v1.8.0 adds container-aware monitoring for SSH sessions that end inside recognized Docker, Podman or Kubernetes application containers. It uses cgroup CPU capacity and memory counters when available; the card uses container CPU pressure, or a throttling fallback when supported, in place of host load. This does not make a connection to the host automatically become an individual-container monitor. Expand the card to inspect busy process names, then compare the same target and interval in the terminal. Missing metrics remain a compatibility check for your environment.

If the connection drops, v1.8.0 keeps the last readings with a Connection interrupted state. Frozen values are not fresh evidence that the service is healthy. Reconnect and wait for new samples before comparing. For historical alerts, fleet dashboards, GPU/VRAM readings or Kubernetes-wide observability, keep the appropriate dedicated tool.

Try one authorized, non-critical target before purchasing: confirm the endpoint, version, account and sampling scope, then check a busy and an idle interval. Monitoring requires Pro or an eligible trial. Download the website edition or review website lifetime Pro. The public China App Store listing was still 1.7.6 when checked on October 24, 2026; do not assume its build includes the v1.8.0 changes.

Docker says no space left: locate the full filesystem

Choose what you have observed on the Linux host running Docker. This checklist stays in your browser; it does not connect, upload output or delete anything.

A Docker pull, database write or deployment can fail even when your Mac or hypervisor has plenty of free storage. Check the Linux filesystem used by the failing process. Run the following inside the intended remote shell, after confirming that its Docker client targets the daemon on that same host. A Docker context or DOCKER_HOST override can otherwise point elsewhere.


hostname
docker info --format 'daemon={{.Name}} root={{.DockerRootDir}} driver={{.Driver}}'
df -h
df -i
docker system df

These checks are read-only, but the output can contain internal hostnames, paths and image names. Review it locally before sharing. Inspect the filesystem containing the exact path in the error, not only the first line of df. If necessary, use df -h and df -i with that existing path as a quoted argument.

Bytes full, inodes full, or only the guest full?

Docker root is not always the whole storage picture

Docker documents separate image and snapshot storage under /var/lib/containerd with the containerd image store, the default on fresh Docker Engine 29+ installations. Volumes and other daemon data can remain under /var/lib/docker. Custom data-root settings, upgraded installations and rootless daemons differ. Use the actual configuration; do not assume that checking or moving one directory covers every store.

For the affected container, replace the example name and inspect only its mount mapping:


docker inspect --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}' your-container

This shows where mounted data comes from; it does not prove a backup exists. A bind mount may live outside Docker's data directory. Docker system df -v provides more detail on Docker objects, but a reclaimable number is not a deletion approval. Stopped containers and unattached volumes can still hold valuable recovery data. Do not use a blanket prune with volumes, delete Docker directories or truncate database files as an unexplained first response.

Once the owner and capacity limit are known, choose a reviewed retention, rotation or expansion plan. Afterwards, recheck the same filesystem and verify the actual application request or failed deployment. More free space alone does not prove the service recovered. Nexus Shell can keep the remote terminal, Docker view and configuration files together; it is not an automatic cleanup or volume-resize tool.

4. Fix the configuration at its source

If the application requires a variable that the container was created without, restarting that same container does not add it. Update the deployment definition or launch command, then recreate only the affected service using your normal reviewed deployment process. An interactive shell edit inside a container is not a durable configuration change.

For a Compose-managed application, update its Compose or environment configuration and use your established deployment procedure. For a disposable test container, create a replacement with the required setting. Production recreation can interrupt service or lose unpersisted data, so the example below is deliberately limited to a new, network-isolated lab container with no mounted volumes.

Reproduce the example without touching an existing service

The following commands create two uniquely named containers. If either name already exists, choose different names; do not delete an unfamiliar container to make room. The first exits with status 1, which is the intended failure. Run each block separately.


docker run --name nexus-seo-docker-lab-20260910 --network none alpine:3.22 sh -c 'test -n "$APP_MODE" || { echo "APP_MODE is required" >&2; exit 1; }; echo "configuration accepted"; sleep 600'

docker run -d --name nexus-seo-docker-fixed-20260910 --network none -e APP_MODE=demo alpine:3.22 sh -c 'test -n "$APP_MODE" || { echo "APP_MODE is required" >&2; exit 1; }; echo "configuration accepted"; sleep 600'
docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}}' nexus-seo-docker-fixed-20260910
docker logs --tail 10 nexus-seo-docker-fixed-20260910

The corrected fixture should report running and configuration accepted. It is a configuration check, not a real web service or proof that an application is healthy. For a real service, also verify its health check and an actual request through the expected route.

Recorded verification, September 10, 2026

We ran this failure and correction through Nexus Shell's SSH command bridge to a loopback Mac host, targeting a disposable Colima Linux VM with Docker Engine 29.2.1. Registry access failed, so our test used Alpine's official 3.22.0 ARM64 minirootfs, verified against its published SHA-256 and imported as a local lab image instead of the registry image in the commands above. This verifies the shell condition and Docker state transition; it is not an external VPS or graphical Docker-view test.


/nexus-seo-docker-lab-20260910 status=exited exit=1
Failure log: APP_MODE is required
/nexus-seo-docker-fixed-20260910 status=running exit=0
Corrected log: configuration accepted

After inspecting the example, remove only the two containers you created:


docker rm nexus-seo-docker-lab-20260910
docker stop nexus-seo-docker-fixed-20260910
docker rm nexus-seo-docker-fixed-20260910

Where Nexus Shell helps

For repeated investigations, Nexus Shell combines SSH terminals, Docker container details and logs, server monitoring and SFTP in one Mac workspace. Connect to your test server, select its Docker view, inspect the affected container and logs, then use its terminal for commands that need more context. Only open a remote file when the evidence points to that file.

Docker management, monitoring and SFTP require Pro or an eligible trial. Basic SSH is free for personal, non-commercial use. Nexus Shell requires Apple Silicon and macOS 14.2 or later. If the command-line workflow already meets your needs, there is no need to change clients.

Optional AI assistance uses Agent Bridge in the direct-download or Homebrew build. Ask your own agent to inspect the test host and explain the evidence before proposing changes. Terminal tools create visible tabs; headless exec does not. Bridge tools do not return stored passwords or private keys, but commands inherit the SSH account's permissions and their output can still contain sensitive information.

Questions before you choose a workflow

Do I need Docker installed on my Mac?

Not when you run Docker commands inside the remote SSH shell. The Docker client and daemon in this guide are on the Linux host. A local Docker context over SSH is another workflow; keep track of which daemon it targets.

Is this a replacement for Docker Desktop or Portainer?

No. This guide investigates an existing remote engine. Nexus Shell adds a Mac server workspace; choose your runtime, deployment system and orchestration tools separately.

Does giving an AI agent a connection ID make commands read-only?

No. A connection ID avoids returning stored credentials through the bridge. It does not create a command sandbox or remove the remote account's write permissions.

Try it on your test server

Repeat the inspection with a host you control. If switching between logs, terminal and files is a recurring part of your work, try the website edition's seven-day Pro trial. Eligible Nexus Shell account registration starts that trial; no card is required and it ends without an automatic charge. Website Pro is lifetime-only, with current pricing shown on the official pricing page.

Prefer Apple purchase management or annual billing? The Mac App Store edition offers lifetime Pro and an auto-renewing annual subscription; any trial follows Apple's displayed eligibility and renewal terms. It does not require a Nexus Shell account and does not include Agent Bridge. Purchases and data are managed separately, and connections do not migrate automatically. Website lifetime pricing is planned to align with App Store lifetime pricing; no new amount or effective date is announced here.

Sources