Dokumentacja DigitalOcean
Wszystkie fragmenty używają curl i python3 (bez zewnętrznych zależności). Przed uruchomieniem ustaw DO_TOKEN.
Zgodność z powłokami: Wszystkie argumenty python3 -c są w apostrofach, więc ani bash, ani zsh nie rozwija zmiennych. Kod Pythona używa tylko podwójnych cudzysłowów.
Ustawianie DO_TOKEN: W zsh zapis DO_TOKEN=... curl ... w jednej linii NIE działa: $DO_TOKEN jest rozwijany, zanim przypisanie zacznie obowiązywać. Użyj jednego z tych sposobów:
export DO_TOKEN=... # then run snippets normally
DO_TOKEN=...; curl ... # semicolon separates assignment from command
Obsługa błędów: Wszystkie fragmenty sprawdzają, czy API DigitalOcean nie zwróciło błędu, zanim sięgną po dane. Błędy DO mają format {"id": "...", "message": "..."}.
Rozmiary instancji
Wyświetl i przefiltruj dostępne rozmiary:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/sizes?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "sizes" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
region, vcpus, mem_gb = "nyc3", 2, 2
for s in sorted(data["sizes"], key=lambda x: (x["vcpus"], x["memory"], x["price_monthly"])):
if not s["available"] or region not in s["regions"]:
continue
if s["vcpus"] == vcpus and s["memory"] == mem_gb * 1024:
slug, vc, mem, disk, price = s["slug"], s["vcpus"], s["memory"]/1024, s["disk"], s["price_monthly"]
print(f"{slug:30s} {vc}vCPU {mem:.0f}GB {disk}GB ${price}/mo")
'
Dostosuj region, vcpus i mem_gb do swoich potrzeb. Użyj >= zamiast ==, żeby dopasować minimum.
Wyszukiwanie obrazów
Znajdź slug najnowszego Ubuntu LTS:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/images?type=distribution&per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "images" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
ubuntu = [i for i in data["images"] if i["distribution"] == "Ubuntu" and "LTS" in i.get("description", "")]
ubuntu.sort(key=lambda x: x["slug"], reverse=True)
for i in ubuntu[:5]:
slug, desc = i["slug"], i["description"]
print(f"{slug:30s} {desc}")
'
Wyszukiwanie regionów
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/regions" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "regions" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
for r in data["regions"]:
if r["available"]:
slug, name = r["slug"], r["name"]
print(f"{slug:10s} {name}")
'
Podstawowy profil dropleta
- Region:
nyc3 - Obraz: najnowsze Ubuntu LTS dostępne przez API
- Plan: Basic / Regular (Shared CPU)
- Przykładowy rozmiar: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB transferu (ok. 24 USD/mies.)
- Nazwa:
wlsly-<adjective>-<surname>(wygenerowana generatorem nazw poniżej)
Klucze SSH
Wyświetl klucze:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/account/keys?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "ssh_keys" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
for k in data["ssh_keys"]:
kid, name, fp = k["id"], k["name"], k["fingerprint"]
print(f"{kid:>10} {name:30s} {fp}")
'
Wgraj nowy klucz:
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "wlycli", "public_key": "'"$(cat ~/.ssh/wlsly_do.pub)"'" }' \
"https://api.digitalocean.com/v2/account/keys"
Projekty
Wyświetl projekty:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/projects?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "projects" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
for p in data["projects"]:
default = " (default)" if p.get("is_default") else ""
pid, name = p["id"], p["name"]
print(f"{pid} {name}{default}")
'
Przypisz zasoby do projektu:
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "resources": ["do:droplet:<droplet-id>", "do:dbaas:<db-id>"] }' \
"https://api.digitalocean.com/v2/projects/<project-id>/resources"
URN-y zasobów projektu
- Droplet:
do:droplet:<id> - Zarządzana baza danych:
do:dbaas:<id>
Generator nazw
Wygeneruj losową nazwę w stylu Dockera z prefiksem wlsly- dla dropletów i baz danych:
WLSLY_NAME=wlsly-$(shuf -n1 -e agile bold brave bright calm clever cool daring eager fair fast fierce fond frank gifted glad grand happy hardy keen kind lively lucid merry modest noble pensive proud quick quiet sharp shrewd sleek smart solid steady stoic sunny swift tender vivid warm wise witty zen)-$(shuf -n1 -e agnesi babbage bell bohr brahmagupta buck carson chatelet chebyshev curie darwin dijkstra easley einstein euler fermat feynman galois gauss goldberg goodall hamilton hawking hodgkin hopper hypatia jackson johnson kare keller knuth leavitt lovelace lumiere mahavira mandela mccarthy meitner mendel mirzakhani morse nash neumann nightingale noether northcutt panini pare pascal pasteur pike poincare ramanujan ride ritchie rosalind sagan shannon snyder swartz tesla thompson turing villani wescoff wing wozniak wright yalow)
echo "$WLSLY_NAME"
Użyj tej samej wygenerowanej nazwy dla dropleta i jego zarządzanej bazy danych (jeśli ją tworzysz).
Tworzenie dropleta
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name":"'"$WLSLY_NAME"'",
"region":"<region>",
"size":"<size-slug>",
"image":"<ubuntu-image-slug>",
"ssh_keys":["<fingerprint>"],
"monitoring":true,
"tags":["wlsly"]
}' \
"https://api.digitalocean.com/v2/droplets" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "droplet" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
d = data["droplet"]
print("Droplet ID:", d["id"])
print("Name:", d["name"])
print("Status:", d["status"])
'
Sprawdzanie IP dropleta
Droplet nie dostanie adresu IP od razu. Odpytuj go, aż pojawi się status=active:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/droplets/<droplet-id>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "droplet" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
d = data["droplet"]
print("Status:", d["status"])
for net in d.get("networks", {}).get("v4", []):
print("IPv4 (%s): %s" % (net["type"], net["ip_address"]))
'
DNS (zarządzany przez DigitalOcean)
Jeśli DNS jest zarządzany przez DigitalOcean, agent może tworzyć i aktualizować rekordy przez API, gdy droplet dostanie adres IP.
Sprawdź, czy domena już istnieje w DO:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/domains/<domain>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "domain" in data:
print("Domain: %s (exists)" % data["domain"]["name"])
else:
print("Domain not found - needs to be created")
if "message" in data:
print("API:", data["message"])
'
Utwórz domenę (tylko raz, jeśli nie została znaleziona):
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"<domain>"}' \
"https://api.digitalocean.com/v2/domains"
Wyświetl istniejące rekordy domeny:
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/domains/<domain>/records?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "domain_records" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
for r in data["domain_records"]:
rid, rtype, name, rdata = r["id"], r["type"], r["name"], r["data"]
print(f"{rid:>10} {rtype:6s} {name:30s} {rdata}")
'
Utwórz rekord A dla domeny głównej (@):
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"@","data":"<droplet-ip>","ttl":300}' \
"https://api.digitalocean.com/v2/domains/<domain>/records"
Utwórz rekord A dla subdomeny storage (wymagany, gdy nie ma CDN):
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"storage","data":"<droplet-ip>","ttl":300}' \
"https://api.digitalocean.com/v2/domains/<domain>/records"
Zaktualizuj istniejący rekord (jeśli rekord już istnieje):
curl -sS -X PUT \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"data":"<droplet-ip>"}' \
"https://api.digitalocean.com/v2/domains/<domain>/records/<record-id>"
Zanim uruchomisz certbota w trybie standalone, upewnij się, że rekordy A/AAAA dla <domain> (oraz storage.<domain>, jeśli jest używana) wskazują na IP dropleta.
Flagi CLI: --public-ip (nadpisuje wykryte IP) i --skip-dns-preflight (pomija sprawdzenie).
Jeśli na potrzeby tymczasowej konfiguracji musisz użyć certyfikatów z własnym podpisem, dodaj --self-signed-certs (generuje certyfikaty z własnym podpisem).
Uwagi o DNS
Jeśli CDN nie jest skonfigurowany, platforma używa storage.<domain>. Ustaw rekordy A/AAAA na IP dropleta albo CNAME na domenę główną, jeśli twój dostawca DNS na to pozwala.
Przygotowanie hosta (SSH)
Ważne: Do zdalnego uruchamiania wieloliniowych skryptów zawsze używaj ssh host 'bash -s' << 'EOF' (heredoc w apostrofach). Nigdy nie przekazuj skryptów jako argumentów ssh host "..." w podwójnych cudzysłowach: lokalna powłoka (zwłaszcza zsh) rozwinie $(...), wzorce [...] i $VARIABLES, zanim wyśle je na zdalny host.
ssh -o StrictHostKeyChecking=accept-new root@<droplet-ip> 'bash -s' << 'PROVISION'
set -euo pipefail
adduser --disabled-password --gecos "Welley Service" wlsly
usermod -aG sudo wlsly
echo "wlsly ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/wlsly
chmod 440 /etc/sudoers.d/wlsly
mkdir -p /home/wlsly/.ssh
cp /root/.ssh/authorized_keys /home/wlsly/.ssh/authorized_keys
chown -R wlsly:wlsly /home/wlsly/.ssh
chmod 700 /home/wlsly/.ssh
chmod 600 /home/wlsly/.ssh/authorized_keys
apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
usermod -aG docker wlsly
echo "=== Verify ==="
docker --version
docker compose version
id wlsly
PROVISION
Potem przełącz się na użytkownika innego niż root:
ssh -o StrictHostKeyChecking=accept-new wlsly@<droplet-ip> 'echo "Connected as $(whoami)"'
Zarządzany Postgres (DigitalOcean)
Dane logowania poznasz dopiero po utworzeniu bazy. Najpierw utwórz bazę, potem odczytaj dane połączenia.
Utwórz:
curl -sS -X POST \
-H "Authorization: Bearer $DO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name":"'"$WLSLY_NAME"'",
"engine":"pg",
"version":"16",
"region":"nyc3",
"size":"db-s-1vcpu-2gb",
"num_nodes":1
}' \
"https://api.digitalocean.com/v2/databases" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "database" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
db = data["database"]
print("DB ID:", db["id"])
print("Status:", db["status"])
'
Odpytuj, aż baza będzie online (trwa to 3-5 minut):
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/databases/<db-id>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "database" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
db = data["database"]
print("Status:", db["status"])
if db["status"] == "online":
conn = db.get("connection", {})
priv = db.get("private_connection", {})
print("Host:", conn.get("host"))
print("Private host:", priv.get("host"))
print("Port:", conn.get("port"))
print("Database:", conn.get("database"))
print("User:", conn.get("user"))
'
Pobierz hasło użytkownika bazy (hasło jest dostępne tylko z tego endpointu):
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
"https://api.digitalocean.com/v2/databases/<db-id>/users" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "users" not in data:
print("Error:", data.get("message", data)); sys.exit(1)
for u in data["users"]:
print("User:", u["name"], "Password:", u.get("password", "N/A"), "Role:", u.get("role", "N/A"))
'
Zwrócone wartości host, port, database, user i password podaj w:
--postgres-host, --postgres-port, --postgres-database, --postgres-user, --postgres-password.
Jeśli hasła brakuje albo wygasło, zresetuj uwierzytelnianie użytkownika bazy.
Wdrażanie przez Welley CLI (przykłady)
Podstawowe wdrożenie z sekretami:
wlycli deployment create \
--domain=example.com \
--email=admin@example.com \
--update-period=15min \
--env=stage \
--otel-endpoint=https://otlp-gateway-prod-us-east-2.grafana.net/otlp \
--otel-use-grpc=false \
--otel-headers='<OTEL_AUTH_HEADERS>' \
--smtp-host=email-smtp.us-east-2.amazonaws.com \
--smtp-user=<MAIL_USER> \
--smtp-password=<MAIL_PASSWORD> \
--smtp-from=noreply@mail.example.com \
--language-service=internal \
--translation-service=deepl \
--translation-service-api-key=<DEEPL_KEY> \
--with-sentry
Zewnętrzny Postgres:
wlycli deployment create \
--domain=example.com \
--email=admin@example.com \
--postgres-host=<DB_HOST> \
--postgres-port=<DB_PORT> \
--postgres-user=<DB_USER> \
--postgres-database=<DB_NAME> \
--postgres-password=<DB_PASSWORD>
Przed uruchomieniem certbota upewnij się, że rekordy DNS A/AAAA kierują <domain> (oraz storage.<domain>, jeśli jest używana) na IP dropleta.