Manage Docker-based dev containers on remote Linux servers. Use this skill whenever the user wants to create, query, or delete a dev container on a remote host, or mentions phrases like "create a dev container", "setup a development environment on server X", "I need a container with N cores and M gigs of memory on IP", "check my dev containers", "list containers on server", "show resources on the remote box", "delete my dev container". This skill handles the full lifecycle: resource detection, NUMA-aware CPU allocation, authentication configuration (password, key, or both), container creation with OpenEuler, and multi-user coexistence on shared servers. Trigger even if the user does not say "container" explicitly but describes allocating isolated CPU/memory on a remote Linux machine for development work.
Manage Docker-based dev containers on remote Linux servers. Use this skill whenever the user wants to create, query, or delete a dev container on a remote host, or mentions phrases like "create a dev container", "setup a development environment on server X", "I need a container with N cores and M gigs of memory on IP", "check my dev containers", "list containers on server", "show resources on the remote box", "delete my dev container". This skill handles the full lifecycle: resource detection, NUMA-aware CPU allocation, authentication configuration (password, key, or both), container creation with OpenEuler, and multi-user coexistence on shared servers. Trigger even if the user does not say "container" explicitly but describes allocating isolated CPU/memory on a remote Linux machine for development work.
Dev Container Manager
Create, query, and manage Docker-based development containers on remote Linux
servers with NUMA-aware resource allocation and multi-user support.
Core principles
Multi-user safe: Every container is named dev_container_<username>.
CPU ranges, memory allocations, and SSH ports are tracked per container so
users never conflict.
NUMA-aware: Consecutive CPU cores are always allocated from a single
NUMA node to avoid cross-socket latency.
User-chosen authentication: Before creating a container, always ask the
user to choose one of three SSH authentication modes: password-only,
key-only, or password+key. Never assume a default — the choice must be
explicit. For key-only and password+key modes, generate a fresh ed25519 key
pair; the private key stays in the current project directory. For
password-only mode, no key pair is generated.
Verify before create: Always check remote resources first. If the
server cannot satisfy the request, tell the user immediately with specifics
(how many cores free, how much memory available, which NUMA nodes are
options).
Workflow: Creating a dev container
Step 1: Gather requirements
From the user's natural-language request, extract:
Parameter
Default
Notes
server
required
IP or hostname
user
root
SSH user for the remote host
cores
16
Must be consecutive, same NUMA node
memory
32g
In GiB
image
must ask user
List available images on server first, then let user choose; default openeuler/openeuler:24.03-lts-sp3 if they accept
If any critical parameter is missing, ask the user. For obvious defaults
(cores, memory), assume the default and mention it in passing; only
stop to ask if the user's request is genuinely ambiguous.
image is never defaulted. The agent MUST list available images on the
server and ask the user to choose. Present the image list alongside auth_method
and install_tools questions.
IMPORTANT — auth_method is never defaulted. The user MUST explicitly
choose the authentication method. After gathering all other parameters and
presenting the plan summary, ask:
If the user already expressed a preference (e.g. "使用密码登录" or "生成密钥"),
do not re-ask — use the expressed preference. If the user mentioned
password, also ask what root password to use.
Step 2: SSH connection precheck
Before running any remote commands, verify that the agent can SSH to the
server silently (key-pair auth, no password prompt). Password-based SSH
does not work for automated/non-interactive use — the agent cannot type a
password.
The bundled script scripts/setup_ssh_key.sh handles the full flow:
local key check, key generation if needed, ssh-copy-id (or manual SCP
fallback), and final verification — with clear per-step output.
After the user confirms success, retry the silent check. If it still
fails, the script's output will already have diagnostics; report them.
Edge case — both sides locked: If PasswordAuthentication is
disabled on the server AND the key isn't authorized yet, the script
can't copy the key. Read the local public key (cat ~/.ssh/id_ed25519.pub)
and tell the user:
The script tests connectivity to registry-1.docker.io, hub.docker.com,
and index.docker.io. Two outcomes:
Hub reachable — No action needed, proceed to Step 4.
Hub unreachable — Check if registry mirrors already exist in
/etc/docker/daemon.json. If none, auto-configure mirrors:
Report to the user: "Docker Hub 不可访问,正在添加镜像加速器..."
Use the known working mirror list:
https://docker.1ms.run
https://dockerproxy.net
https://proxy.vvvv.ee
https://dockerproxy.link
Update daemon.json. If the file already has other config (like
data-root), merge the mirrors into it — never overwrite existing keys.
If the file doesn't exist, create it with just the mirrors.
Restart Docker: systemctl restart docker (or service docker restart).
Verify Docker is back up and retry the hub check. If still unreachable,
warn the user but continue (the mirrors may still work for cached images).
Merge logic example:
ssh <user>@<server> 'python3 -c "
import json, os
cfg = {}
if os.path.exists(\"/etc/docker/daemon.json\"):
with open(\"/etc/docker/daemon.json\") as f:
cfg = json.load(f)
cfg[\"registry-mirrors\"] = [
\"https://docker.1ms.run\",
\"https://dockerproxy.net\",
\"https://proxy.vvvv.ee\",
\"https://dockerproxy.link\"
]
with open(\"/etc/docker/daemon.json\", \"w\") as f:
json.dump(cfg, f, indent=2)
" && systemctl restart docker'
3b: Disk space check
The script checks free space on three paths:
docker_root — Actual configured Docker data root (from daemon.json's
data-root, or default /var/lib/docker). This is where images and
container layers live.
var_lib_docker — The default /var/lib/docker path. Always checked
even when data-root is customized, because the system partition
hosting this path may still be tight for Docker logs, temporary overlays,
or fallback operations.
tmp — /tmp, used by container builds and image layer extraction.
Threshold: 128 GB. If any of the three paths has less than 128 GB free:
Note: if docker_root and var_lib_docker share the same mount point,
merge them into one row to avoid confusion.
List alternative paths from the precheck output (mount points with
128 GB free). If only var_lib_docker_low_space is warned but
docker_root already has ample space, the Docker data directory is
already on a large partition — mention this and suggest no migration
is needed for Docker, but note the system partition is tight for other
uses.
If docker_data_root_low_space is in warnings, ask the user: "是否将
Docker 数据目录迁移到 <推荐路径>?"
If user agrees to migrate, update the Docker data directory:
ssh <user>@<server> 'python3 -c "
import json, os
cfg = {}
if os.path.exists(\"/etc/docker/daemon.json\"):
with open(\"/etc/docker/daemon.json\") as f:
cfg = json.load(f)
cfg[\"data-root\"] = \"<new_path>/docker_image\"
with open(\"/etc/docker/daemon.json\", \"w\") as f:
json.dump(cfg, f, indent=2)
" && mkdir -p <new_path>/docker_image && systemctl restart docker'
If user declines, note the risk and continue (image pulls may fail).
If all three paths have >128 GB free, report "磁盘空间充足" and proceed.
Step 4: Check remote resources
Run the bundled resource check script on the remote server. This collects:
CPU topology (NUMA nodes, core lists, free cores per node), total/free
memory, existing dev containers and their resource allocations, Docker
status, used ports, and available Docker images.
The script is at scripts/check_resources.sh. Read it, then pipe it to the
remote host:
After presenting the resource summary and image list, ask the user to choose:
请选择要使用的镜像:
openeuler/openeuler:24.03-lts-sp3(默认)
dev-openeuler:latest
ubuntu:22.04
其他(请指定)
If the user specified an image in their initial request, use that and skip
the question. If only one image is available on the server, note it and use
it unless the user specifies otherwise. The default openeuler/openeuler:24.03-lts-sp3
is always listed as an option — Docker will pull it if not present locally.
If resources are insufficient, report the shortfall clearly and stop. Example:
"该服务器仅有 8 个空闲核心(分布于不同 NUMA 节点),无法分配连续 16 核。"
Step 5: Allocate resources
Pick resources that don't conflict with existing containers.
CPU allocation strategy:
List all NUMA nodes. For each node, determine which core ranges are
already taken by existing dev_container_* containers.
Within the node with the most free cores, find a contiguous block of
cores cores. Prefer higher-numbered cores (less likely to conflict with
system processes). Example: if node0 has cores 0-63 and 0-31 are free,
allocate 48-63 (the last 16) rather than 0-15.
If no single NUMA node has enough consecutive free cores, report which
nodes have how many and let the user decide.
Memory allocation strategy:
Check free -h output. If requested memory exceeds available, report it.
Docker --memory flag provides hard limit; also set --memory-swap to
the same value (no swap).
Port allocation strategy:
Scan from 2222 upward. The first port not used by any existing container
and not bound on the host wins.
Command: for port in $(seq 2222 2300); do ss -tlnp | grep -q ":$port " || { echo $port; break; }; done
Step 6: Handle authentication
If auth_method is password:
Skip key generation entirely.
When running the creation script, omit PUBKEY and pass ROOT_PASSWORD
and AUTH_MODE=password.
The SSH config must use PasswordAuthentication yes, PermitRootLogin yes.
When running the creation script, pass PUBKEY with the public key content
and AUTH_MODE=key.
The SSH config must use PasswordAuthentication no,
PermitRootLogin prohibit-password.
If auth_method is both:
Generate ed25519 key pair locally (same as key-only).
Also ask the user for a root password.
When running the creation script, pass both PUBKEY and ROOT_PASSWORD
with AUTH_MODE=both.
The SSH config must use PasswordAuthentication yes, PermitRootLogin yes.
Step 7: Create the container
Use the bundled scripts/create_container.sh script. Read it first, then
customize and pipe it to the remote host. The script handles:
Checking if the image exists locally; only pulling if absent
Creating the container with --cpuset-cpus, --memory, --memory-swap,
--cpus, --hostname, --restart=unless-stopped, port mapping
Starting the container
Checking if sshd is already present; installing openssh-server only if missing
Optionally installing dev tools (gcc, g++, python3, git, vim, make, cmake,
golang) — controlled by INSTALL_TOOLS=true. Default is to skip dev tools.
Each tool is checked individually and only installed if missing.
Setting root password (if AUTH_MODE is password or both)
Injecting the public key into /root/.ssh/authorized_keys (if AUTH_MODE
is key or both, and PUBKEY is provided)
This format works when copy-pasted from any directory — the $(pwd) captures
the current working directory at the time the command was generated.
Workflow: Deleting a container
When the user asks to delete a container:
Identify the container by username or container name
Stop and remove it: docker rm -f dev_container_<username>
Remove the committed image: docker rmi dev-<username>:latest
Confirm deletion and note that the local private key file still exists
(user can delete it manually if desired)
Ask for confirmation before deleting unless the user's intent is unambiguous.
Multi-user coexistence rules
Naming: Every container is dev_container_<username>. The committed
image is dev-<username>:latest. Entrypoint script is
/entrypoint_<username>.sh.
CPU: Ranges must not overlap. Always compute free ranges by
subtracting all existing --cpuset-cpus allocations from each NUMA node's
full range.
Memory: Sum of all --memory allocations must not exceed host total.
Leave at least 4 GiB for the host.
Ports: Each container gets a unique host port. Track all currently
mapped ports from docker ps output.
Keys: Key filenames include the username:
dev_container_<username>_key and dev_container_<username>_key.pub.
This prevents overwrites when managing multiple containers from the same
local directory.
Resource allocation reference
NUMA node core range calculation
Given NUMA node output like node 0 cpus: 0 1 2 ... 63 and existing
container CPU bindings, compute free cores per node:
Parse each NUMA node's CPU list into a set
Parse each container's --cpuset-cpus range into a set
Subtract allocated cores from each node's set
Find consecutive runs in the remaining set
A run of N consecutive integers is valid for an N-core request.
Port allocation reference
Reserved ports to skip: 0-1023 (system), 2375-2376 (Docker API), 3306
(MySQL), 5432 (PostgreSQL), 6379 (Redis), 8000-9000 (common web services).
Start scanning from 2222; cap at 65535. If no port is available below 10000,
warn the user.
Troubleshooting
Symptom
Likely cause
Fix
Docker Hub unreachable
Network restriction / firewall
Auto-add registry mirrors via env_precheck.sh; if mirrors also fail, user may need proxy configured in ~/.docker/config.json
Docker pull timeout
Mirror not responding
Try removing failing mirrors from daemon.json and restart Docker; or add additional mirrors
Disk space low (< 128G)
Default /var/lib/docker on small system disk
Run disk check from env_precheck.sh; migrate data-root to larger partition
Container exits immediately
/sbin/init missing
Use tail -f /dev/null or sleep infinity as init; commit with entrypoint script
sshd not running after restart
No auto-start mechanism
Commit container with entrypoint script that launches sshd before the idle loop
Permission denied (publickey)
Public key not in authorized_keys or wrong key
Verify key injection via docker exec; check ~/.ssh/authorized_keys permissions (600) and ~/.ssh/ (700)
Permission denied (password)
Password not set or PAM misconfigured
Verify via docker exec <c> sh -c 'echo "root:<pw>" | chpasswd'; ensure /etc/pam.d/sshd exists and /etc/ssh/sshd_config.d/dev.conf has PasswordAuthentication yes and UsePAM yes
Cannot allocate memory
Memory limit too low for dnf/apt
Install tools before setting hard memory limit, or temporarily raise limit during setup
Port already in use
Another container or host service
Pick next available port
NUMA node has gaps in free cores
Other containers fragmenting the node
Try allocating from a less-used NUMA node, or report to user
Docker fails to start after daemon.json change
JSON syntax error or path doesn't exist
Validate JSON with python3 -m json.tool /etc/docker/daemon.json; ensure data-root path exists and is writable
Bundled scripts
Four scripts live under scripts/. Read them before use so you understand
their inputs/outputs. Do not blindly pipe them — customize the environment
variables first.
env_precheck.sh — Runs on remote host. Checks Docker Hub accessibility,
registry mirror configuration, and disk space. Outputs JSON. Run this
before any container creation.
check_resources.sh — Runs on remote host. Collects CPU/NUMA/memory/port
info and existing container allocations. Outputs JSON for parsing.
create_container.sh — Runs on remote host. Expects env vars:
CONTAINER_NAME, IMAGE, CPUSET_CPUS, MEMORY, HOST_PORT,
USERNAME, AUTH_MODE (one of password, key, both). Depending on
AUTH_MODE: PUBKEY (for key/both), ROOT_PASSWORD (for
password/both). Optional: INSTALL_TOOLS=true to install dev tools.
Handles the full create-install-configure-commit pipeline. Skips image pull,
sshd install, and dev tool install if already present.
list_containers.sh — Runs on remote host. Outputs formatted table of all
dev_container_* containers with resource details, auth mode, and
auth-aware SSH connection commands. Detects auth mode from Docker labels
(preferred) or by inspecting sshd config inside the container (fallback).