dockhand-cli 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dockhand/__init__.py +335 -0
- dockhand/build.py +35 -0
- dockhand/client/__init__.py +38 -0
- dockhand/client/base.py +37 -0
- dockhand/client/local.py +45 -0
- dockhand/client/ssh.py +53 -0
- dockhand/config.py +355 -0
- dockhand/constants.py +2 -0
- dockhand/download.py +53 -0
- dockhand/error.py +10 -0
- dockhand/history.py +171 -0
- dockhand/manage.py +459 -0
- dockhand/queue.py +174 -0
- dockhand/resubmit.py +49 -0
- dockhand/submit.py +163 -0
- dockhand/sync.py +50 -0
- dockhand/tagging.py +48 -0
- dockhand/transport.py +212 -0
- dockhand/tunnel.py +62 -0
- dockhand/volumes.py +149 -0
- dockhand_cli-0.3.1.dist-info/METADATA +478 -0
- dockhand_cli-0.3.1.dist-info/RECORD +25 -0
- dockhand_cli-0.3.1.dist-info/WHEEL +4 -0
- dockhand_cli-0.3.1.dist-info/entry_points.txt +2 -0
- dockhand_cli-0.3.1.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dockhand-cli
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: CLI for managing Docker containers on remote machines
|
|
5
|
+
Project-URL: Repository, https://github.com/NicholasPHansen/dockhand
|
|
6
|
+
Author: Nicholas P. Hansen
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: fabric>=3.2.2
|
|
11
|
+
Requires-Dist: gitpython>=3.1.43
|
|
12
|
+
Requires-Dist: paramiko>=3.4.1
|
|
13
|
+
Requires-Dist: typer>=0.15.4
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Dockhand
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
A CLI tool for managing Docker containers on remote machines. Build, run, and manage Docker containers with a single command from your local machine.
|
|
23
|
+
|
|
24
|
+
**Why this project?**
|
|
25
|
+
|
|
26
|
+
Managing Docker workloads across multiple machines is painful: keeping code in sync, remembering which version runs where, handling build/run/debug cycles remotely.
|
|
27
|
+
|
|
28
|
+
`dockhand` solves this by letting you manage everything from your laptop — code syncs automatically, builds happen on the remote, and results can be downloaded back. No more juggling multiple git repos or SSH sessions.
|
|
29
|
+
|
|
30
|
+
This project is *heavily* inspired by the awesome work from @ChrisFugl's [DTU-HPC-CLI](https://github.com/ChrisFugl/DTU-HPC-CLI).
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Fast Iteration**: Code is mounted into the container at runtime — no rebuild needed when your code changes
|
|
35
|
+
- **Job Queue**: All workloads are submitted through [task spooler](https://viric.name/soft/ts/) so jobs run in order without competing for resources
|
|
36
|
+
- **Slot Reservations**: Declare how many CPU slots a job needs so heavy jobs don't block each other
|
|
37
|
+
- **Remote Docker Management**: Build and run Docker containers on a remote machine via SSH
|
|
38
|
+
- **Local or Remote**: Automatically detect localhost vs remote, or explicitly configure
|
|
39
|
+
- **Port Forwarding**: Establish SSH tunnels to access container ports locally
|
|
40
|
+
- **File Syncing**: Automatically sync your local code to the remote before running
|
|
41
|
+
- **Volume Management**: Mount data directories into containers and download results back
|
|
42
|
+
- **Stable Job IDs**: Local job IDs increment independently of the host, so IDs are unambiguous across machines
|
|
43
|
+
- **Quick Resubmit**: Easily rerun previous jobs with the same or different parameters
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
### Local machine
|
|
48
|
+
|
|
49
|
+
- Python 3.10+
|
|
50
|
+
- [git](https://git-scm.com/) — used for branch tracking and code sync
|
|
51
|
+
|
|
52
|
+
### Docker host (remote or local)
|
|
53
|
+
|
|
54
|
+
- [Docker](https://docs.docker.com/engine/install/) — container runtime
|
|
55
|
+
- [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) (`nvidia-docker`) — required only if using GPUs (`--gpus`)
|
|
56
|
+
- [task spooler (`tsp`)](https://viric.name/soft/ts/) — job queue daemon
|
|
57
|
+
|
|
58
|
+
Installing task spooler on Ubuntu/Debian:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sudo apt-get install task-spooler
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On other systems the binary may be called `ts` instead of `tsp` — check your package manager.
|
|
65
|
+
|
|
66
|
+
**Configuring the total slot count** (optional, recommended for multi-user setups):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Set the number of available slots to match CPU cores (run once on the host)
|
|
70
|
+
tsp -S 16
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Jobs default to 1 slot each. Use `--slots` to reserve more (see [Slot Reservations](#slot-reservations)).
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
**From source with uv (recommended):**
|
|
78
|
+
|
|
79
|
+
[Install uv](https://docs.astral.sh/uv/):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then clone and sync:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/nicholasphansen/dockhand.git
|
|
89
|
+
cd dockhand
|
|
90
|
+
uv sync
|
|
91
|
+
uv run dockhand --help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Or with pip:**
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install dockhand
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
All commands work with the same `.dockhand.json` configuration file. See [Configuration](#configuration) below.
|
|
103
|
+
|
|
104
|
+
### Available Commands
|
|
105
|
+
|
|
106
|
+
| Command | Description |
|
|
107
|
+
|---------|-------------|
|
|
108
|
+
| `submit` | Build the image and queue a container run |
|
|
109
|
+
| `run` | Queue a container run from an already-built image |
|
|
110
|
+
| `install` | Build the Docker image without running it |
|
|
111
|
+
| `jobs` | List active (running/queued) jobs, with start/end/duration — use `--all` for finished jobs too |
|
|
112
|
+
| `logs` | Show logs from a job, preceded by a running/finished duration line — use `--follow`/`-f` to stream live |
|
|
113
|
+
| `stop` | Stop a **running** job |
|
|
114
|
+
| `remove` | Remove a **queued** job before it starts |
|
|
115
|
+
| `urgent` | Promote a queued job to the front of the queue |
|
|
116
|
+
| `prune` | Remove baked images no longer used by an active job |
|
|
117
|
+
| `history` | Show history of all past runs |
|
|
118
|
+
| `volumes` | List the full container filesystem as a tree |
|
|
119
|
+
| `download` | Download a file from a docker volume |
|
|
120
|
+
| `resubmit` | Resubmit a previous job with optional overrides |
|
|
121
|
+
| `tunnel` | Forward container ports to localhost via SSH tunnel |
|
|
122
|
+
|
|
123
|
+
All commands that accept a job ID default to the most recent job if no ID is given.
|
|
124
|
+
|
|
125
|
+
### Typical workflow
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 1. Build the image once (or when dependencies change)
|
|
129
|
+
dockhand install
|
|
130
|
+
|
|
131
|
+
# 2. Iterate freely — submit syncs code and queues a run, no rebuild needed
|
|
132
|
+
dockhand submit 'python train.py --epochs 10'
|
|
133
|
+
dockhand submit 'python train.py --epochs 10' # code changed? just submit again
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Examples
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Sync code and queue a run
|
|
140
|
+
dockhand submit 'python train.py --epochs 10'
|
|
141
|
+
|
|
142
|
+
# Reserve 4 CPU slots for a parallel job
|
|
143
|
+
dockhand submit --slots 4 'python train.py --workers 4'
|
|
144
|
+
|
|
145
|
+
# Queue a run with GPUs and port mappings
|
|
146
|
+
dockhand submit --gpus all -p 6006:6006 'python train.py'
|
|
147
|
+
|
|
148
|
+
# Queue a run without syncing (code already up to date)
|
|
149
|
+
dockhand submit --no-sync 'python train.py'
|
|
150
|
+
|
|
151
|
+
# Queue a run from an already-built image, skip sync
|
|
152
|
+
dockhand run 'python train.py --epochs 20'
|
|
153
|
+
|
|
154
|
+
# Build image (run once, or when dependencies change)
|
|
155
|
+
dockhand install
|
|
156
|
+
# Show full build output
|
|
157
|
+
dockhand install -v
|
|
158
|
+
# Build a specific Dockerfile
|
|
159
|
+
dockhand install --dockerfile Dockerfile.prod
|
|
160
|
+
|
|
161
|
+
# Check active jobs
|
|
162
|
+
dockhand jobs
|
|
163
|
+
|
|
164
|
+
# Check all jobs including finished
|
|
165
|
+
dockhand jobs --all
|
|
166
|
+
|
|
167
|
+
# Stream logs from the last job
|
|
168
|
+
dockhand logs --follow
|
|
169
|
+
|
|
170
|
+
# Show the last 50 lines from job #3
|
|
171
|
+
dockhand logs 3 --n 50
|
|
172
|
+
|
|
173
|
+
# Stop the last running job
|
|
174
|
+
dockhand stop
|
|
175
|
+
|
|
176
|
+
# Remove a queued job before it starts
|
|
177
|
+
dockhand remove 4
|
|
178
|
+
|
|
179
|
+
# Promote job #5 to the front of the queue
|
|
180
|
+
dockhand urgent 5
|
|
181
|
+
|
|
182
|
+
# Forward container ports to localhost
|
|
183
|
+
dockhand tunnel
|
|
184
|
+
|
|
185
|
+
# See all past runs
|
|
186
|
+
dockhand history
|
|
187
|
+
|
|
188
|
+
# Browse mounted volumes as a tree
|
|
189
|
+
dockhand volumes --depth 2
|
|
190
|
+
|
|
191
|
+
# Download results
|
|
192
|
+
dockhand download results/model.pth
|
|
193
|
+
|
|
194
|
+
# Resubmit the latest job with different GPUs
|
|
195
|
+
dockhand resubmit --gpus 2
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Note:** If you've installed dockhand globally, you can omit `uv run`.
|
|
199
|
+
|
|
200
|
+
`jobs` shows Started/Ended/Duration for each job, and `logs` prints a one-line
|
|
201
|
+
`running for 5m30s` / `finished in 12m45s` header before the log output. Neither the
|
|
202
|
+
queue nor docker exposes exact start/end timestamps cheaply, so these are recorded the
|
|
203
|
+
first time `jobs` or `logs` happens to observe a job as running or finished — a job
|
|
204
|
+
never checked on while running will show no duration once it's done.
|
|
205
|
+
|
|
206
|
+
Resubmitting a job that ran in [`bake`](#code-delivery-mount-vs-bake) mode reruns the exact
|
|
207
|
+
image it originally built — recorded per job — rather than rebuilding from current code, so a
|
|
208
|
+
past run reproduces faithfully. (Overriding `--imagename` opts out and uses the new image.)
|
|
209
|
+
|
|
210
|
+
## Slot Reservations
|
|
211
|
+
|
|
212
|
+
When multiple users share a Docker host, jobs may need different amounts of CPU resources. The `--slots` option maps to `tsp -N <n>`, which tells task spooler how many of the host's total slots a job should consume before it is allowed to start.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Lightweight job — uses 1 slot (default)
|
|
216
|
+
dockhand run 'python eval.py'
|
|
217
|
+
|
|
218
|
+
# Parallel job — blocks 8 slots while running
|
|
219
|
+
dockhand run --slots 8 'python train.py --workers 8'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The total number of slots available on the host is set with `tsp -S <n>` (run once by the system admin). A job only starts when enough free slots are available, so heavy jobs naturally queue behind each other without manual coordination.
|
|
223
|
+
|
|
224
|
+
The default slot count can also be set in `.dockhand.json` so it applies to every submission without needing the flag:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"queue": {
|
|
229
|
+
"slots": 4
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Volumes
|
|
235
|
+
|
|
236
|
+
The `volumes` command lists the container filesystem as a unified tree rooted at `containerworkdir`. It searches the code mount (project root → `containerworkdir`) and all configured data volumes on the host directly — no running container required. Files appear at their container paths.
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Show container filesystem (default depth: 5 levels)
|
|
240
|
+
dockhand volumes
|
|
241
|
+
|
|
242
|
+
# Expand to a specific depth; folders at the limit show as collapsed with ...
|
|
243
|
+
dockhand volumes --depth 3
|
|
244
|
+
|
|
245
|
+
# Show filesystem for a specific past job
|
|
246
|
+
dockhand volumes 42
|
|
247
|
+
|
|
248
|
+
# Same via download --list
|
|
249
|
+
dockhand download --list --depth 2
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The `download` command resolves any workdir-relative path (as shown by `volumes`) back to its host location and downloads it via rsync. Directory downloads preserve the full path structure locally.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Download a file
|
|
256
|
+
dockhand download results/model.pth
|
|
257
|
+
|
|
258
|
+
# Download a directory (trailing slash) — files land in reports/figures/ locally
|
|
259
|
+
dockhand download reports/figures/
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Configuration
|
|
263
|
+
|
|
264
|
+
Create a `.dockhand.json` file in your project root.
|
|
265
|
+
|
|
266
|
+
### Minimal Configuration
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"docker": {
|
|
271
|
+
"dockerfile": "Dockerfile",
|
|
272
|
+
"imagename": "my-image",
|
|
273
|
+
"volumes": []
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### SSH Configuration
|
|
279
|
+
|
|
280
|
+
To run Docker commands on a remote machine:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"ssh": {
|
|
285
|
+
"user": "your_username",
|
|
286
|
+
"identityfile": "/path/to/private/key",
|
|
287
|
+
"hostname": "remote.example.com"
|
|
288
|
+
},
|
|
289
|
+
"docker": { "..." }
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Hostname detection:
|
|
294
|
+
|
|
295
|
+
- No `ssh` config, or hostname resolves to `127.0.0.1` → runs locally
|
|
296
|
+
- Otherwise → connects via SSH
|
|
297
|
+
|
|
298
|
+
### Docker Configuration
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"sync": true,
|
|
303
|
+
"ssh": { "..." },
|
|
304
|
+
"queue": {
|
|
305
|
+
"enabled": true,
|
|
306
|
+
"slots": 1
|
|
307
|
+
},
|
|
308
|
+
"docker": {
|
|
309
|
+
"dockerfile": "Dockerfile",
|
|
310
|
+
"imagename": "my-image",
|
|
311
|
+
"volumes": [
|
|
312
|
+
{
|
|
313
|
+
"hostpath": "/local/data",
|
|
314
|
+
"containerpath": "/data",
|
|
315
|
+
"permissions": "rw"
|
|
316
|
+
}
|
|
317
|
+
],
|
|
318
|
+
"ports": ["8080:80"],
|
|
319
|
+
"gpus": "all",
|
|
320
|
+
"containerworkdir": "/",
|
|
321
|
+
"preserve_paths": [".venv"]
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Top-level options:**
|
|
327
|
+
|
|
328
|
+
| Option | Description | Default |
|
|
329
|
+
|--------|-------------|---------|
|
|
330
|
+
| `sync` | Rsync local code to remote before building | `true` |
|
|
331
|
+
|
|
332
|
+
**Queue sub-config options:**
|
|
333
|
+
|
|
334
|
+
| Option | Description | Default |
|
|
335
|
+
|--------|-------------|---------|
|
|
336
|
+
| `enabled` | Submit jobs through the task-spooler queue | `false` |
|
|
337
|
+
| `tool` | Queue backend | `task_spooler` |
|
|
338
|
+
| `slots` | Default queue slots to reserve per job (overridden by `--slots`) | `1` |
|
|
339
|
+
|
|
340
|
+
When `enabled` is **true**, jobs are queued with `tsp` and run in submission order. When
|
|
341
|
+
**false**, there is no queue — each submit starts immediately as a detached container
|
|
342
|
+
(`docker run -d --name dockhand-<id>`). `logs`, `stop`, `jobs`, and `remove` work the same
|
|
343
|
+
in both modes; they transparently use `tsp` or `docker` per the job's recorded transport.
|
|
344
|
+
`--slots` and `--urgent` are queue-only and are ignored (with a warning) when the queue is off.
|
|
345
|
+
|
|
346
|
+
**Docker sub-config options:**
|
|
347
|
+
|
|
348
|
+
| Option | Description | Default |
|
|
349
|
+
|--------|-------------|---------|
|
|
350
|
+
| `dockerfile` | Path to the Dockerfile | required |
|
|
351
|
+
| `imagename` | Docker image name | required |
|
|
352
|
+
| `volumes` | List of volume mounts | required |
|
|
353
|
+
| `ports` | Port mappings | — |
|
|
354
|
+
| `gpus` | GPU flag passed to `docker run --gpus` | — |
|
|
355
|
+
| `containerworkdir` | Path inside the container where the project is mounted and commands run from | `/` |
|
|
356
|
+
| `preserve_paths` | Subpaths under `containerworkdir` to keep from the image instead of the bind-mounted host copy (e.g. `.venv`, `node_modules`) — only used in `mount` delivery | `[]` |
|
|
357
|
+
| `code_delivery` | How code reaches the container: `mount` or `bake` | derived from `queue.enabled` |
|
|
358
|
+
|
|
359
|
+
> **Note:** `slots` moved from the `docker` block to the `queue` block. A `docker.slots` value is still honored with a deprecation warning.
|
|
360
|
+
|
|
361
|
+
### Code delivery: `mount` vs `bake`
|
|
362
|
+
|
|
363
|
+
`code_delivery` controls how your code gets into the container:
|
|
364
|
+
|
|
365
|
+
- **`mount`** — bind-mount the synced project over `containerworkdir` at run time (with
|
|
366
|
+
`preserve_paths` protecting image-built artifacts). No rebuild per submit, so it's ideal
|
|
367
|
+
for tight edit-run iteration. The code is read when the container *starts*.
|
|
368
|
+
- **`bake`** — build the code into the image and run that image with no code mount. Each
|
|
369
|
+
submit builds (Docker layer caching keeps this cheap when only source changed).
|
|
370
|
+
|
|
371
|
+
When unset it defaults by mode: **`bake` when `queue.enabled` is true, `mount` otherwise.**
|
|
372
|
+
The reason is drift. A queued job may sit in the queue before it runs; with `mount` it would
|
|
373
|
+
pick up whatever code is on disk when it *dequeues*, so editing code after submitting silently
|
|
374
|
+
changes an already-queued job. `bake` pins each queued submit to an immutable, content-addressed
|
|
375
|
+
image tag (derived from the git commit, plus a hash of uncommitted changes) so the job runs
|
|
376
|
+
exactly the code it was submitted with. Without a queue there's no drift window, so `mount`'s
|
|
377
|
+
zero-rebuild iteration wins. Set `code_delivery` explicitly to override the default either way.
|
|
378
|
+
|
|
379
|
+
> Data `volumes` are always mounted regardless of `code_delivery`; only the *code* mount differs.
|
|
380
|
+
> For guaranteed-immutable queued builds, commit before submitting — untracked file *contents*
|
|
381
|
+
> are not folded into the image tag.
|
|
382
|
+
|
|
383
|
+
Each distinct baked submit produces its own image tag, and `resubmit` reruns the exact tag a job
|
|
384
|
+
originally built. Those tags accumulate over time; run `dockhand prune` to remove baked images that
|
|
385
|
+
no running or queued job still references (`--dry-run` to preview, `-y` to skip the prompt).
|
|
386
|
+
|
|
387
|
+
### Profiles
|
|
388
|
+
|
|
389
|
+
Use profiles to switch between different configurations:
|
|
390
|
+
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"docker": {
|
|
394
|
+
"dockerfile": "Dockerfile",
|
|
395
|
+
"imagename": "my-image"
|
|
396
|
+
},
|
|
397
|
+
"profiles": {
|
|
398
|
+
"prod": {
|
|
399
|
+
"queue": { "slots": 8 },
|
|
400
|
+
"docker": {
|
|
401
|
+
"gpus": "all",
|
|
402
|
+
"dockerfile": "Dockerfile.prod"
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
"dev": {
|
|
406
|
+
"queue": { "slots": 2 },
|
|
407
|
+
"docker": {
|
|
408
|
+
"gpus": "1",
|
|
409
|
+
"dockerfile": "Dockerfile.dev"
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
dockhand --profile dev submit 'python train.py'
|
|
418
|
+
dockhand --profile prod submit 'python train.py'
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Complete Example
|
|
422
|
+
|
|
423
|
+
```json
|
|
424
|
+
{
|
|
425
|
+
"sync": true,
|
|
426
|
+
"ssh": {
|
|
427
|
+
"user": "myuser",
|
|
428
|
+
"identityfile": "~/.ssh/id_rsa",
|
|
429
|
+
"hostname": "gpu-server.example.com"
|
|
430
|
+
},
|
|
431
|
+
"docker": {
|
|
432
|
+
"dockerfile": "Dockerfile",
|
|
433
|
+
"imagename": "my-training-app",
|
|
434
|
+
"volumes": [
|
|
435
|
+
{
|
|
436
|
+
"hostpath": "/local/data",
|
|
437
|
+
"containerpath": "/data",
|
|
438
|
+
"permissions": "rw"
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"hostpath": "/local/models",
|
|
442
|
+
"containerpath": "/models",
|
|
443
|
+
"permissions": "rw"
|
|
444
|
+
}
|
|
445
|
+
],
|
|
446
|
+
"ports": ["6006:6006"],
|
|
447
|
+
"gpus": "all",
|
|
448
|
+
"containerworkdir": "/app"
|
|
449
|
+
},
|
|
450
|
+
"queue": {
|
|
451
|
+
"enabled": true,
|
|
452
|
+
"slots": 4
|
|
453
|
+
},
|
|
454
|
+
"remote_path": "/home/myuser/projects/my-app",
|
|
455
|
+
"profiles": {
|
|
456
|
+
"quick": {
|
|
457
|
+
"queue": { "slots": 1 },
|
|
458
|
+
"docker": {
|
|
459
|
+
"gpus": "1"
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## How It Works
|
|
467
|
+
|
|
468
|
+
1. **Build** (`install`): Optionally syncs local code via rsync, then runs `docker build` on the host. Pass `-v` to stream build output; default shows a spinner only. Only needed when dependencies or the Dockerfile change.
|
|
469
|
+
2. **Submit** (`submit`/`run`): Optionally syncs code to the remote, then starts a `docker run`. With the queue enabled it's submitted to task spooler (`tsp`); with the queue disabled it runs immediately as a detached container. How code reaches the container depends on [`code_delivery`](#code-delivery-mount-vs-bake): in `mount` mode the project directory is bind-mounted at `containerworkdir` (no rebuild; `preserve_paths` protects image-built artifacts like a `.venv` from being shadowed by the mount); in `bake` mode the code is built into an image — content-addressed and immutable for queued jobs — and run with no code mount.
|
|
470
|
+
3. **Queue** (when enabled): tsp runs jobs one at a time in submission order. Jobs with `--slots N` only start when N free slots are available. With the queue disabled, jobs start on submit and this ordering doesn't apply.
|
|
471
|
+
4. **Job IDs**: Each submission gets a local job ID (stable, auto-incrementing) stored alongside the host and the transport handle (tsp job number, or container name). The local ID is what all commands accept.
|
|
472
|
+
5. **Logs** (`logs`): Reads from the tsp output file (queued jobs) or via `docker logs` (direct runs). Use `--follow`/`-f` to stream a running job live, `--n N` for the last N lines.
|
|
473
|
+
6. **Port Forwarding** (`tunnel`): Establishes SSH local port forwards for container ports.
|
|
474
|
+
7. **Download**: Maps containerworkdir-relative paths to host paths and uses rsync to fetch files.
|
|
475
|
+
|
|
476
|
+
## License
|
|
477
|
+
|
|
478
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
dockhand/__init__.py,sha256=TYC9DcY9lbbHrdwO8tfpzGN1GVlD9Zz4EenrmgbbZY0,11961
|
|
2
|
+
dockhand/build.py,sha256=Qn8dgiVV6gnyDvrR2XcXgzQLjSED1wdNR0BjTVB5OQU,1270
|
|
3
|
+
dockhand/config.py,sha256=x-w2qNm_qmULYImm3g3_2_CXTbUEvIGfCbEivVdzfsc,12152
|
|
4
|
+
dockhand/constants.py,sha256=UhZG5-BQxYlVEKrT4SeZz58jDTCnswcBpdQ5DdmUmwg,79
|
|
5
|
+
dockhand/download.py,sha256=xqdPd5BMHK35fEwycHknhWohuEjSXVxw8cYYl2MtWrg,2097
|
|
6
|
+
dockhand/error.py,sha256=KC42dv4PkZ6_RwfZCgqkStd3IimJHiUXh0Xfsf6Bwhw,268
|
|
7
|
+
dockhand/history.py,sha256=u5jCIIHZTmEFB7Lw8nc2DbO9fpL8N5xbo_ELXMTHhq0,5442
|
|
8
|
+
dockhand/manage.py,sha256=9M3908uKQULpE8xrggjBbUkBPatsZ7YKLuK3VuZKPb8,18805
|
|
9
|
+
dockhand/queue.py,sha256=bdLy5pDsvpE3XKpxTzRcI8NHadrrWE0tBGjn1zqz1zg,6958
|
|
10
|
+
dockhand/resubmit.py,sha256=lR669pdCw0U-9sXhwaMqiG67ZT2aTqRgjiZdszxI3IU,2101
|
|
11
|
+
dockhand/submit.py,sha256=9YXdrEvzlhECkJN0P017y8TCWbS0Hh0XnzdsBhUfZow,5961
|
|
12
|
+
dockhand/sync.py,sha256=eFiGISfsS8k9BduC7CrIuWk3Nl1fsgeT5RiUiafFqk0,1602
|
|
13
|
+
dockhand/tagging.py,sha256=ra7uCMpS5FkTqdpcuwGU2gmPhwhtp8wEGeWx_yZR07M,1766
|
|
14
|
+
dockhand/transport.py,sha256=lFhwUnG4stSKeKPmUQc6dMilLlbi4uB0j0lHgqqk9q0,8205
|
|
15
|
+
dockhand/tunnel.py,sha256=TfAxsJyLKZAcAaWTeNb-NEi0zs_XKb7nAAHI3NJDOQI,2197
|
|
16
|
+
dockhand/volumes.py,sha256=HOSpadtdHJtSvrnoMoYQs_IbH91cRsbttWl3Mo8xLK0,6107
|
|
17
|
+
dockhand/client/__init__.py,sha256=rV_HIm6Rn5OKhC1getUcPayUvIP-GViMfEU2mqDkcNc,1252
|
|
18
|
+
dockhand/client/base.py,sha256=qq15iiJSpdVL3LEH2sWvoB4ckLI2iIwEXkIlylTDoeo,791
|
|
19
|
+
dockhand/client/local.py,sha256=545a6wwmd4wxhuaHY-K9xax5yk4J10cYUhZjH3m1Bs8,1239
|
|
20
|
+
dockhand/client/ssh.py,sha256=VT6VxSQAnBEO6QPDJFUc-zhDpdeAhEJTP8iy2ybferQ,1653
|
|
21
|
+
dockhand_cli-0.3.1.dist-info/METADATA,sha256=tYCEhakHA0UWZKEsF5wM3A2MATFAYA04Yi8Nio0BDrE,17133
|
|
22
|
+
dockhand_cli-0.3.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
23
|
+
dockhand_cli-0.3.1.dist-info/entry_points.txt,sha256=DXUII-tAUFd-fHvPXQCr1VMdzf9L44JjhoY4eCc1Wm8,42
|
|
24
|
+
dockhand_cli-0.3.1.dist-info/licenses/LICENSE,sha256=llmhN1yXJp25KpDwt4YycB4hsTUTF1uDT8ZtF39CfZM,1063
|
|
25
|
+
dockhand_cli-0.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Nicholas Hansen
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|