codeforge-dev 1.4.0
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.
- package/.devcontainer/.env +22 -0
- package/.devcontainer/CHANGELOG.md +197 -0
- package/.devcontainer/CLAUDE.md +117 -0
- package/.devcontainer/README.md +222 -0
- package/.devcontainer/config/main-system-prompt.md +502 -0
- package/.devcontainer/config/settings.json +47 -0
- package/.devcontainer/devcontainer.json +94 -0
- package/.devcontainer/features/README.md +113 -0
- package/.devcontainer/features/agent-browser/README.md +65 -0
- package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
- package/.devcontainer/features/agent-browser/install.sh +79 -0
- package/.devcontainer/features/ast-grep/README.md +24 -0
- package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
- package/.devcontainer/features/ast-grep/install.sh +51 -0
- package/.devcontainer/features/ccstatusline/README.md +296 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
- package/.devcontainer/features/ccstatusline/install.sh +290 -0
- package/.devcontainer/features/ccusage/README.md +205 -0
- package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccusage/install.sh +132 -0
- package/.devcontainer/features/claude-code/README.md +498 -0
- package/.devcontainer/features/claude-code/config/settings.json +36 -0
- package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
- package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
- package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
- package/.devcontainer/features/claude-code/install.sh +466 -0
- package/.devcontainer/features/claude-monitor/README.md +74 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
- package/.devcontainer/features/claude-monitor/install.sh +99 -0
- package/.devcontainer/features/lsp-servers/README.md +85 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
- package/.devcontainer/features/lsp-servers/install.sh +116 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
- package/.devcontainer/features/mcp-qdrant/README.md +474 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
- package/.devcontainer/features/mcp-reasoner/README.md +177 -0
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
- package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
- package/.devcontainer/features/notify-hook/README.md +86 -0
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
- package/.devcontainer/features/notify-hook/install.sh +38 -0
- package/.devcontainer/features/splitrail/README.md +140 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
- package/.devcontainer/features/splitrail/install.sh +129 -0
- package/.devcontainer/features/tree-sitter/README.md +138 -0
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
- package/.devcontainer/features/tree-sitter/install.sh +173 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
- package/.devcontainer/scripts/setup-aliases.sh +80 -0
- package/.devcontainer/scripts/setup-config.sh +28 -0
- package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
- package/.devcontainer/scripts/setup-plugins.sh +80 -0
- package/.devcontainer/scripts/setup.sh +58 -0
- package/LICENSE.txt +674 -0
- package/README.md +267 -0
- package/package.json +44 -0
- package/setup.js +83 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# Container Lifecycle -- Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. Container Create and Run Options
|
|
4
|
+
|
|
5
|
+
### Full run() Signature (Key Parameters)
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
container = client.containers.run(
|
|
9
|
+
image="python:3.12-slim",
|
|
10
|
+
command="python app.py",
|
|
11
|
+
name="my-app",
|
|
12
|
+
detach=True,
|
|
13
|
+
|
|
14
|
+
# Networking
|
|
15
|
+
ports={"8000/tcp": 8080, "8443/tcp": ("127.0.0.1", 8443)},
|
|
16
|
+
network="app-net",
|
|
17
|
+
hostname="app-host",
|
|
18
|
+
|
|
19
|
+
# Environment
|
|
20
|
+
environment={"DEBUG": "1", "DB_URL": "sqlite:///data.db"},
|
|
21
|
+
working_dir="/app",
|
|
22
|
+
user="1000:1000",
|
|
23
|
+
|
|
24
|
+
# Storage
|
|
25
|
+
volumes={"/host/data": {"bind": "/data", "mode": "rw"}},
|
|
26
|
+
mounts=[Mount(target="/config", source="/host/config", type="bind", read_only=True)],
|
|
27
|
+
tmpfs={"/tmp": "size=100m"},
|
|
28
|
+
read_only=True,
|
|
29
|
+
|
|
30
|
+
# Lifecycle
|
|
31
|
+
remove=True, # auto-remove on exit
|
|
32
|
+
init=True, # use tini as PID 1
|
|
33
|
+
restart_policy={"Name": "on-failure", "MaximumRetryCount": 3},
|
|
34
|
+
stop_signal="SIGTERM",
|
|
35
|
+
|
|
36
|
+
# Resources
|
|
37
|
+
mem_limit="512m",
|
|
38
|
+
nano_cpus=500_000_000, # 0.5 CPU
|
|
39
|
+
|
|
40
|
+
# Labels
|
|
41
|
+
labels={"app": "web", "version": "1.0"},
|
|
42
|
+
|
|
43
|
+
# Health
|
|
44
|
+
healthcheck={
|
|
45
|
+
"test": ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"],
|
|
46
|
+
"interval": 10_000_000_000,
|
|
47
|
+
"timeout": 5_000_000_000,
|
|
48
|
+
"retries": 3,
|
|
49
|
+
"start_period": 30_000_000_000,
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### create() vs run()
|
|
55
|
+
|
|
56
|
+
`create()` accepts the same parameters as `run()` but does not start the container:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
container = client.containers.create("alpine", command="echo hello", name="one-off")
|
|
60
|
+
# Configure further if needed...
|
|
61
|
+
container.start()
|
|
62
|
+
output = container.wait()
|
|
63
|
+
logs = container.logs()
|
|
64
|
+
container.remove()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use `create()` + `start()` when:
|
|
68
|
+
- Pre-start inspection or configuration is needed.
|
|
69
|
+
- Attaching to streams before start.
|
|
70
|
+
- Explicit lifecycle control is required.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. exec_run Patterns
|
|
75
|
+
|
|
76
|
+
### Basic Execution
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
result = container.exec_run("python -c 'print(1+1)'")
|
|
80
|
+
assert result.exit_code == 0
|
|
81
|
+
assert result.output == b"2\n"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Streaming Output
|
|
85
|
+
|
|
86
|
+
For long-running commands or real-time output:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
result = container.exec_run(
|
|
90
|
+
"python train_model.py --epochs 100",
|
|
91
|
+
stream=True,
|
|
92
|
+
demux=True,
|
|
93
|
+
)
|
|
94
|
+
for stdout_chunk, stderr_chunk in result.output:
|
|
95
|
+
if stdout_chunk:
|
|
96
|
+
print(stdout_chunk.decode(), end="")
|
|
97
|
+
if stderr_chunk:
|
|
98
|
+
print(f"ERR: {stderr_chunk.decode()}", end="")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With `stream=True`, `result.output` is a generator. With `demux=True`, each yielded item is a tuple `(stdout_bytes | None, stderr_bytes | None)`.
|
|
102
|
+
|
|
103
|
+
### Detached Execution
|
|
104
|
+
|
|
105
|
+
Fire-and-forget commands:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
result = container.exec_run("cleanup.sh", detach=True)
|
|
109
|
+
# result.output is None; command runs in background
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Interactive-Style Execution
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
result = container.exec_run(
|
|
116
|
+
"/bin/sh -c 'cd /app && python manage.py migrate'",
|
|
117
|
+
environment={"DJANGO_SETTINGS_MODULE": "config.production"},
|
|
118
|
+
workdir="/app",
|
|
119
|
+
user="django",
|
|
120
|
+
)
|
|
121
|
+
if result.exit_code != 0:
|
|
122
|
+
raise RuntimeError(f"Migration failed: {result.output.decode()}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Full exec_run Signature
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
container.exec_run(
|
|
129
|
+
cmd, # str or list
|
|
130
|
+
stdout=True, # attach stdout
|
|
131
|
+
stderr=True, # attach stderr
|
|
132
|
+
stdin=False, # attach stdin
|
|
133
|
+
tty=False, # allocate pseudo-TTY
|
|
134
|
+
privileged=False, # extended privileges
|
|
135
|
+
user="", # user identity
|
|
136
|
+
detach=False, # run in background
|
|
137
|
+
stream=False, # return generator
|
|
138
|
+
socket=False, # return raw socket
|
|
139
|
+
environment=None, # dict of env vars
|
|
140
|
+
workdir=None, # working directory
|
|
141
|
+
demux=False, # separate stdout/stderr
|
|
142
|
+
)
|
|
143
|
+
# Returns: ExecResult(exit_code=int, output=bytes|generator|tuple)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 3. Log Streaming
|
|
149
|
+
|
|
150
|
+
### Real-Time Tailing
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import threading
|
|
154
|
+
|
|
155
|
+
def tail_logs(container):
|
|
156
|
+
for line in container.logs(stream=True, follow=True):
|
|
157
|
+
print(f"[{container.name}] {line.decode().strip()}")
|
|
158
|
+
|
|
159
|
+
thread = threading.Thread(target=tail_logs, args=(container,), daemon=True)
|
|
160
|
+
thread.start()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Time-Bounded Logs
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from datetime import datetime, timedelta
|
|
167
|
+
|
|
168
|
+
# Last hour
|
|
169
|
+
recent = container.logs(since=datetime.utcnow() - timedelta(hours=1))
|
|
170
|
+
|
|
171
|
+
# Between two timestamps
|
|
172
|
+
logs = container.logs(
|
|
173
|
+
since=datetime(2024, 1, 1),
|
|
174
|
+
until=datetime(2024, 1, 2),
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Separated Streams
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
stdout_only = container.logs(stdout=True, stderr=False)
|
|
182
|
+
stderr_only = container.logs(stdout=False, stderr=True)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Full logs() Signature
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
container.logs(
|
|
189
|
+
stdout=True, # include stdout
|
|
190
|
+
stderr=True, # include stderr
|
|
191
|
+
stream=False, # return generator (blocking)
|
|
192
|
+
timestamps=False, # prefix lines with timestamps
|
|
193
|
+
tail="all", # "all" or int (number of lines)
|
|
194
|
+
since=None, # datetime, epoch int, or float
|
|
195
|
+
follow=False, # keep connection open for new output
|
|
196
|
+
until=None, # datetime, epoch int, or float
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 4. Health Check Configuration
|
|
203
|
+
|
|
204
|
+
### Setting Health Checks at Creation
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
container = client.containers.run(
|
|
208
|
+
"myapp:latest",
|
|
209
|
+
detach=True,
|
|
210
|
+
healthcheck={
|
|
211
|
+
"test": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
|
|
212
|
+
"interval": 10_000_000_000, # 10 seconds (nanoseconds)
|
|
213
|
+
"timeout": 5_000_000_000, # 5 seconds
|
|
214
|
+
"retries": 3,
|
|
215
|
+
"start_period": 30_000_000_000, # 30 seconds grace
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
All time values are in **nanoseconds**. Helpers:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
SECOND = 1_000_000_000
|
|
224
|
+
MINUTE = 60 * SECOND
|
|
225
|
+
|
|
226
|
+
healthcheck = {
|
|
227
|
+
"test": ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"],
|
|
228
|
+
"interval": 15 * SECOND,
|
|
229
|
+
"timeout": 5 * SECOND,
|
|
230
|
+
"retries": 3,
|
|
231
|
+
"start_period": 1 * MINUTE,
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Test formats:
|
|
236
|
+
- `["CMD", "executable", "arg1"]` -- run command directly.
|
|
237
|
+
- `["CMD-SHELL", "command string"]` -- run via `/bin/sh -c`.
|
|
238
|
+
- `["NONE"]` -- disable health checking.
|
|
239
|
+
|
|
240
|
+
### Disabling Health Checks
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
container = client.containers.run("myapp", detach=True, healthcheck={"test": ["NONE"]})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 5. Health Status Polling
|
|
249
|
+
|
|
250
|
+
### Reading Health Status
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
container.reload() # refresh cached state from daemon
|
|
254
|
+
health = container.attrs["State"]["Health"]
|
|
255
|
+
|
|
256
|
+
print(health["Status"]) # "healthy", "unhealthy", "starting"
|
|
257
|
+
print(health["FailingStreak"])
|
|
258
|
+
for log_entry in health["Log"]:
|
|
259
|
+
print(f" Exit: {log_entry['ExitCode']}, Output: {log_entry['Output'][:100]}")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Polling Until Healthy
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import time
|
|
266
|
+
|
|
267
|
+
def wait_for_healthy(container, timeout=60, interval=2):
|
|
268
|
+
"""Poll container health status until healthy or timeout."""
|
|
269
|
+
deadline = time.monotonic() + timeout
|
|
270
|
+
while time.monotonic() < deadline:
|
|
271
|
+
container.reload()
|
|
272
|
+
health = container.attrs.get("State", {}).get("Health", {})
|
|
273
|
+
status = health.get("Status", "none")
|
|
274
|
+
if status == "healthy":
|
|
275
|
+
return True
|
|
276
|
+
if status == "unhealthy":
|
|
277
|
+
logs = health.get("Log", [])
|
|
278
|
+
last = logs[-1] if logs else {}
|
|
279
|
+
raise RuntimeError(
|
|
280
|
+
f"Container unhealthy: exit={last.get('ExitCode')}, "
|
|
281
|
+
f"output={last.get('Output', '')[:200]}"
|
|
282
|
+
)
|
|
283
|
+
time.sleep(interval)
|
|
284
|
+
raise TimeoutError(f"Container did not become healthy within {timeout}s")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Async Health Polling
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
import asyncio
|
|
291
|
+
|
|
292
|
+
async def wait_for_healthy_async(container, timeout=60, interval=2):
|
|
293
|
+
deadline = asyncio.get_event_loop().time() + timeout
|
|
294
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
295
|
+
container.reload()
|
|
296
|
+
status = container.attrs.get("State", {}).get("Health", {}).get("Status", "none")
|
|
297
|
+
if status == "healthy":
|
|
298
|
+
return True
|
|
299
|
+
if status == "unhealthy":
|
|
300
|
+
raise RuntimeError("Container unhealthy")
|
|
301
|
+
await asyncio.sleep(interval)
|
|
302
|
+
raise TimeoutError(f"Health check timeout after {timeout}s")
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 6. Wait and Timeout
|
|
308
|
+
|
|
309
|
+
### Blocking Wait
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
result = container.wait(timeout=120, condition="not-running")
|
|
313
|
+
# Returns: {"StatusCode": 0, "Error": None}
|
|
314
|
+
|
|
315
|
+
if result["StatusCode"] != 0:
|
|
316
|
+
logs = container.logs(tail=50)
|
|
317
|
+
raise RuntimeError(f"Container failed (exit {result['StatusCode']}): {logs.decode()}")
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`condition` options: `"not-running"` (default), `"next-exit"`, `"removed"`.
|
|
321
|
+
|
|
322
|
+
### Timeout Handling
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
from requests.exceptions import ReadTimeout
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
result = container.wait(timeout=30)
|
|
329
|
+
except ReadTimeout:
|
|
330
|
+
container.stop(timeout=5)
|
|
331
|
+
container.remove(force=True)
|
|
332
|
+
raise TimeoutError("Container did not exit within 30s")
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 7. Auto-Remove Patterns
|
|
338
|
+
|
|
339
|
+
### Ephemeral Containers
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
# Auto-remove on exit (daemon-side)
|
|
343
|
+
output = client.containers.run(
|
|
344
|
+
"python:3.12-slim",
|
|
345
|
+
command="python -c 'print(42)'",
|
|
346
|
+
remove=True,
|
|
347
|
+
)
|
|
348
|
+
print(output) # b"42\n"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
With `remove=True` and `detach=False` (default), the SDK blocks until the container exits, returns the output, and the daemon removes the container automatically.
|
|
352
|
+
|
|
353
|
+
### Detached with Auto-Remove
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
container = client.containers.run(
|
|
357
|
+
"worker:latest",
|
|
358
|
+
detach=True,
|
|
359
|
+
remove=True,
|
|
360
|
+
)
|
|
361
|
+
# Container auto-removes when it exits; container.wait() still works
|
|
362
|
+
result = container.wait()
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Caveat: after auto-removal, `container.logs()` and `container.reload()` raise `NotFound`. Capture logs before waiting if needed.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## 8. Prune
|
|
370
|
+
|
|
371
|
+
Remove stopped containers, unused images, volumes, and networks:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
# Stopped containers
|
|
375
|
+
result = client.containers.prune(filters={"until": "24h"})
|
|
376
|
+
print(result["SpaceReclaimed"])
|
|
377
|
+
|
|
378
|
+
# Dangling images
|
|
379
|
+
client.images.prune(filters={"dangling": True})
|
|
380
|
+
|
|
381
|
+
# Unused volumes
|
|
382
|
+
client.volumes.prune()
|
|
383
|
+
|
|
384
|
+
# Unused networks
|
|
385
|
+
client.networks.prune()
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Prune returns a dict with `SpaceReclaimed` (bytes) and the list of deleted resource IDs.
|