Support creating droplets from snapshots (clone workflow)
评估
调研方向
从 create 命令和 wake 使用的现有 api.create_droplet_from_snapshot() 方法开始。跟踪 create 如何渲染、发送和监控 cloud-init,然后实现快照路径,使其跳过这些步骤,同时保留等待 active 状态、SSH 配置和项目分配。完成标准是:--from-snapshot 创建 droplet 时不使用 cloud-init,并且与 --image 互斥。
由索引模型根据 Issue 内容生成。
描述
AI Agent with Greg: We wanted to snapshot a perfectly configured droplet and spin up 10 clones from it — like a sysadmin photocopier. Turns out dropkit's create command always injects cloud-init, which re-runs on the snapshot and causes chaos (user creation fails, .zshrc gets overwritten, unconditional reboot). Time to teach dropkit the art of cloning. 🧬🖨️
Use Case
Snapshot → Clone N droplets — a common workflow for:
- Spinning up pre-configured build/test environments
- Creating identical workshop/training machines
- Scaling a known-good configuration quickly
# The dream:
dropkit create my-worker-1 --from-snapshot 12345678 --size s-4vcpu-8gb
dropkit create my-worker-2 --from-snapshot 12345678 --size s-4vcpu-8gb
# ... or even:
for i in $(seq 1 10); do
dropkit create "worker-$i" --from-snapshot 12345678
done
The Problem
dropkit create always renders and sends cloud-init user_data to the DigitalOcean API. When creating from a snapshot:
- Cloud-init re-runs — DO assigns a new droplet ID → instance-ID mismatch → cloud-init treats it as first boot
- The template is NOT idempotent — several critical issues:
users:directive fails or is skipped if user already existswrite_files:overwrites.zshrc(loses user customizations)runcmd:ends with unconditionalrebootgit config --globalresets any user-modified values
Cloud-init is fundamentally a provisioning tool, not an idempotent configuration manager. Making the template fully idempotent is possible but would be a significant effort touching every directive.
Current State
| Component | Exists? | Notes |
|---|---|---|
api.create_droplet_from_snapshot() |
✅ Yes | Used by wake command, takes snapshot ID, no user_data |
dropkit create --image |
✅ Yes | But always sends cloud-init; image is a slug, not snapshot ID |
dropkit wake |
✅ Yes | Restores from hibernation snapshot only (expects dropkit-<name> naming + metadata tags) |
| Snapshot-based create without cloud-init | ❌ No | The missing piece |
Proposed Approaches
Option A: --from-snapshot <id> flag on create (Recommended — simplest)
Add a --from-snapshot flag to dropkit create that:
- Uses
api.create_droplet_from_snapshot()instead ofapi.create_droplet() - Skips cloud-init rendering and sending entirely
- Skips cloud-init completion monitoring
- Still performs: wait for active, SSH config setup, project assignment
- Optionally still runs Tailscale setup (snapshot may not have it)
# Mutually exclusive with --image
@app.command()
def create(
...
from_snapshot: int | None = typer.Option(
None, "--from-snapshot",
help="Create from snapshot ID (skips cloud-init)"
),
...
):
Pros: Minimal change (~30 lines), reuses existing API method, clear intent
Cons: Slightly different code path within create, snapshot ID must be known by user
Option B: --no-cloud-init flag (More general)
A flag to skip cloud-init regardless of image source. Combined with --image <snapshot-id>:
dropkit create my-box --image 12345678 --no-cloud-init
Pros: More composable, works with any image scenario
Cons: Two flags needed, easy to forget --no-cloud-init with a snapshot (leading to the reboot-of-doom)
Option C: Make cloud-init template idempotent (Long-term)
Refactor the template to be safe for re-execution:
- Guard user creation:
id {{ username }} || useradd ... - Use marker files:
[ -f /etc/dropkit/.initialized ] || ... - Remove unconditional
reboot; usecloud-init-per instance - Make
write_filesconditional or append-only
Pros: dropkit create --image <snapshot-id> "just works"
Cons: Significant template refactor, hard to test all edge cases, changes behavior for fresh installs too
Option D: New dropkit clone command (Most ergonomic)
Dedicated command for the clone workflow:
dropkit clone my-worker --from my-golden-image --count 5 --size s-4vcpu-8gb
Pros: Best UX, can add clone-specific features (auto-naming, parallel creation)
Cons: Largest scope, new command surface area
Recommendation
Start with Option A (--from-snapshot). It's the smallest change, reuses existing infrastructure, and solves the immediate need. Options C and D can follow later as enhancements.
Happy to implement whichever approach the team prefers!
🤖 Generated with Claude Code — your AI that learned the hard way that cloud-init and snapshots are like mixing sudo with optimism
- 主要语言
- Python
- 星标
- 128
- 派生
- 14
- 平均合并
- 10 小时 13 分钟
- 30 天内合并 PR
- 4
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
trailofbits/dropkit 的其他 Issue
-
Publish on PyPI?未关闭
难度 3/5 1-2 天 新手友好度 55/100
trailofbits/dropkit#73 · 1 个 reaction ·
-
难度 4/5 3-5 天 新手友好度 45/100
trailofbits/dropkit#58 · 1 条评论 · 1 个 reaction ·
查看 trailofbits/dropkit 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 75/100
common-workflow-language/cwl-utils#477 ·
维护者通常 1 天内回复
-
Add dependabot未关闭feat
难度 2/5 1 小时以内 新手友好度 72/100
open-nudge/opentemplate#204 ·
维护者通常 1 天内回复
-
github-stale-ci-pr-closer: KV state is double-wrapped, never persists, and hits the depth-32 limit可能已有人在做 @VascoSch92 今天认领。 未关闭priority:medium ready-for-dev
难度 2/5 1-3 小时 新手友好度 72/100
OpenHands/extensions#781 ·
维护者通常 1 天内回复
-
bug good first issue
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
curriculum documentation quality
难度 2/5 1-3 小时 新手友好度 78/100
githubnext/gh-aw-workshop#4638 ·
维护者通常 1 天内回复