Cloning an EC2 Instance: Three Ways

AWS console offers three ways to duplicate an EC2 instance, differing in whether disk data is carried over.

Create AMI (recommended, full clone) — preserves the system disk, installed software, and all configuration.

In the EC2 console, select the target instance → Actions → Image and templates → Create image. Wait for the AMI status to become available (a few minutes to tens of minutes), then go to AMIs → select it → Launch instance from AMI. Adjust instance type, subnet, Security Group as needed.

Launch More Like This (fastest, no data) — copies only instance configuration (type, SG, subnet, tags). The system disk is brand new.

Actions → Image and templates → Launch more like this. The Launch page opens with config pre-filled; confirm and launch. Good for stateless instances, e.g. web servers initialized via userdata.

Launch Template — if the original instance had a Launch Template saved, launch directly from it. EC2 → Launch Templates → select template → Actions → Launch instance from template.

Use AMI for most cases. Use Launch More Like This when you only need the same specs with a clean disk.

照着现有 EC2 开一台一样的:三种方式

AWS 控制台里有三种复制 EC2 的方式,区别在于是否携带磁盘数据。

创建 AMI(推荐,完整克隆) — 保留系统盘数据、已安装软件和所有配置。

EC2 控制台选中目标实例 → Actions → Image and templates → Create image。等 AMI 状态变为 available(几分钟到几十分钟),再到 AMIs 页面选中它 → Launch instance from AMI,按需调整 instance type、subnet、Security Group 即可。

Launch More Like This(最快,但不含数据) — 只复制实例规格配置(type、SG、subnet、tags),系统盘是全新的。

Actions → Image and templates → Launch more like this,进入 Launch 页面时配置已预填好,确认启动就行。适合无状态实例,比如用 userdata 初始化的 web server。

Launch Template — 如果原实例之前保存过 Launch Template,可以直接从模板启动。EC2 → Launch Templates → 选模板 → Actions → Launch instance from template。

大多数场景用 AMI,只需要同规格全新系统时用 Launch More Like This。

Why Vite dev server needs a proxy (and production doesn't)

In development, your frontend runs on localhost:5173 and your API server on localhost:3000. The browser blocks cross-origin requests — that's CORS. Vite's dev proxy solves this by forwarding /api/* requests to the backend, making them look same-origin to the browser:

// vite.config.ts
server: {
  proxy: {
    '/api': 'http://localhost:3000'
  }
}

In production this proxy disappears. The built frontend is just static files (HTML/JS/CSS) — no port, no process. Nginx or a CDN serves them, and reverse-proxies /api/* to the backend the same way Vite did in dev:

user → Nginx :80
         ├── /api/*  → backend :3000
         └── /*      → dist/ static files

One port from the user's perspective, no CORS issue. The backend port is always real and needed; the frontend "port" only exists during development because Vite's dev server is a live process.

Linux suddenly preferring IPv6 and breaking connectivity? Fix gai.conf

Your Linux box resolves a host to its AAAA (IPv6) record, connects, but the remote isn't actually listening on IPv6. You see telnet hang on an IPv6 address. This happens when your system starts preferring IPv6 over IPv4.

One-off fix — force IPv4 for a single command:

telnet -4 api.z.ai 80

Permanent fix — edit /etc/gai.conf and uncomment this line:

precedence ::ffff:0:0/96  100

This tells getaddrinfo() to return IPv4-mapped addresses with higher precedence than IPv6. Changes take effect immediately — no restart needed. gai.conf is re-read on every getaddrinfo() call, so the next DNS lookup picks up the new rule. Existing connections are unaffected.

If /etc/gai.conf doesn't exist, just create it with that single line.

`--data-raw "$VAR"` silently drops multipart body on Windows Git Bash

When posting multipart form data via curl in a bash script, it's tempting to build the body in a variable and pass it with --data-raw "$DATA". On Linux this often works fine. On Windows Git Bash, it silently breaks — the server receives the request but fields like body come through empty.

Two things go wrong at once. First, Windows-style paths (C:\Users\...) passed to bash utilities like tail and file are misread — the backslashes get misinterpreted and the file read returns nothing. Fix that with cygpath:

filename=$(cygpath -u "$1" 2>/dev/null || echo "$1")

Second, even with the right path, storing the body in a shell variable and passing it via --data-raw is unreliable. Shell variable expansion, CRLF handling, and platform-specific curl behavior all interact badly. The body gets mangled before it reaches the wire.

The fix is to bypass variables entirely: write the multipart payload to a temp file and send it with --data-binary @file. Since the post body is already in a file, tail it directly into the temp file instead of slurping it into a variable:

TMPBODY=$(mktemp)
TMPDATA=$(mktemp)

tail -n +2 "$filename" > "$TMPBODY"

{
  printf "%s\r\n" "--${BOUNDARY}"
  printf "Content-Disposition: form-data; name=\"body\"\r\n\r\n"
  cat "$TMPBODY"
  printf "\r\n%s\r\n" "--${BOUNDARY}--"
} > "$TMPDATA"

curl ... --data-binary "@$TMPDATA"

rm -f "$TMPBODY" "$TMPDATA"

--data-binary @file sends the exact bytes on disk — no shell expansion, no line ending conversion. Works the same on Linux, macOS, and Windows Git Bash.