Ένας πράκτορας κώδικα λέει ότι η λειτουργία δουλεύει. Ίσως να ισχύει. Τα τεστ που έγραψε περνάνε, και αυτό αποδεικνύει λιγότερα απ' όσα θα θέλαμε όλοι. Αυτό που πραγματικά θέλετε είναι να το ανοίξετε στον browser πριν πλησιάσει τους χρήστες, χωρίς να ζητήσετε από τον πράκτορα να αγγίξει την παραγωγή.
Τα περιβάλλοντα προεπισκόπησης το λύνουν, και δεν χρειάζεστε πλατφόρμα γι' αυτά. Ένας VPS με δική του IPv4, Docker, Caddy και δύο σύντομα scripts δίνουν σε κάθε branch δικό του URL, όπως https://feature-login-form.preview.example.com, πίσω από κωδικό και με νέο build σε κάθε push. Ο πράκτορας παίρνει ένα κλειδί SSH που μπορεί να ξεκινά και να σταματά προεπισκοπήσεις και τίποτε άλλο.
Το στήσαμε σε server δοκιμών και περάσαμε όλο τον κύκλο πριν το γράψουμε: δημιουργία, άνοιγμα, νέο push, κατάργηση, απόπειρα κατάχρησης του κλειδιού.
Τι χρειάζεστε
- Έναν VPS με δημόσια IPv4. Το AI-Agent-IP χωράει αρκετές προεπισκοπήσεις ταυτόχρονα.
- Μια wildcard εγγραφή DNS:
*.preview.example.com→ η IP του server. - Το αποθετήριο της εφαρμογής σας με ένα
Dockerfileπου σερβίρει στη θύρα 8080. Οποιαδήποτε γλώσσα κάνει. - Ubuntu 24.04 ή Debian 12/13 με
apt install -y docker.io caddy git.
Κλωνοποιήστε το αποθετήριο μία φορά στο /srv/app, με deploy key μόνο ανάγνωσης.
Τα δύο scripts
Το /usr/local/bin/preview-up χτίζει ένα branch και το βάζει online:
#!/usr/bin/env bash
# preview-up <branch> — build the branch and open it at https://<name>.preview.example.com
set -euo pipefail
BRANCH="$1"
NAME=$(printf '%s' "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
DIR=/srv/previews/$NAME
git -C /srv/app fetch --quiet origin "$BRANCH"
rm -rf "$DIR"; git -C /srv/app worktree prune
git -C /srv/app worktree add --quiet --force --detach "$DIR" FETCH_HEAD
docker build --quiet -t "preview-$NAME" "$DIR" >/dev/null
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker run -d --name "preview-$NAME" --restart unless-stopped --memory 512m -p 127.0.0.1::8080 "preview-$NAME" >/dev/null
PORT=$(docker port "preview-$NAME" 8080/tcp | head -1 | awk -F: '{print $NF}')
cat > "/etc/caddy/previews/$NAME.caddy" <<CADDY
$NAME.preview.example.com {
import preview_common
reverse_proxy 127.0.0.1:$PORT
}
CADDY
systemctl reload caddy
echo "https://$NAME.preview.example.com"
Το /usr/local/bin/preview-down το αφαιρεί:
#!/usr/bin/env bash
# preview-down <branch> — remove the branch preview
set -euo pipefail
NAME=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker image rm "preview-$NAME" >/dev/null 2>&1 || true
rm -f "/etc/caddy/previews/$NAME.caddy"; rm -rf "/srv/previews/$NAME"; git -C /srv/app worktree prune
systemctl reload caddy
Μερικές επιλογές είναι σκόπιμες. Τα containers δημοσιεύουν μόνο στο 127.0.0.1 με τυχαία θύρα, οπότε τίποτα δεν φτάνει σε αυτά παρά μόνο μέσω Caddy. Αυτό μετράει, γιατί οι θύρες που δημοσιεύει το Docker παρακάμπτουν το ufw. Κάθε προεπισκόπηση έχει όριο μνήμης 512 MB, ώστε ένα branch εκτός ελέγχου να μη ρίξει τα υπόλοιπα. Και αν τρέξετε ξανά το ίδιο branch, ξαναχτίζεται επί τόπου: push, up, ανανέωση.
Caddy: ένα αρχείο ανά προεπισκόπηση, ένας κωδικός για όλες
mkdir -p /etc/caddy/previews /srv/previews
chmod +x /usr/local/bin/preview-up /usr/local/bin/preview-down
caddy hash-password --plaintext 'choose-a-review-password'
Βάλτε το hash στο /etc/caddy/Caddyfile:
(preview_common) {
basicauth {
reviewer PASTE-THE-HASH-HERE
}
}
import /etc/caddy/previews/*.caddy
Μετά systemctl reload caddy. Κάθε προεπισκόπηση βρίσκεται πλέον πίσω από την ίδια σύνδεση, οπότε ένα μισοτελειωμένο branch με έξοδο debug δεν μένει ανοιχτό στο internet. Σε νεότερες εκδόσεις του Caddy η οδηγία γράφεται και basic_auth· το basicauth δουλεύει ακόμη και στις δύο.
Το κλειδί του πράκτορα: up και down, τίποτε άλλο
Ο πράκτορας πρέπει να ξεκινά προεπισκοπήσεις, όχι να κατέχει τον server. Δημιουργήστε χρήστη deploy και ένα script-φύλακα που δέχεται ακριβώς δύο εντολές:
#!/usr/bin/env bash
# /usr/local/bin/preview-gate — the only thing the agent's key may run
set -euo pipefail
read -r ACTION BRANCH EXTRA <<< "${SSH_ORIGINAL_COMMAND:-}"
[[ -z "${EXTRA:-}" && "${BRANCH:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]{0,99}$ && "$BRANCH" != *..* ]] || { echo "usage: up|down <branch>" >&2; exit 2; }
case "$ACTION" in
up) exec sudo /usr/local/bin/preview-up "$BRANCH" ;;
down) exec sudo /usr/local/bin/preview-down "$BRANCH" ;;
*) echo "usage: up|down <branch>" >&2; exit 2 ;;
esac
useradd -m -s /bin/bash deploy
echo 'deploy ALL=(root) NOPASSWD: /usr/local/bin/preview-up, /usr/local/bin/preview-down' > /etc/sudoers.d/preview
chmod 440 /etc/sudoers.d/preview && visudo -cf /etc/sudoers.d/preview
chmod 755 /usr/local/bin/preview-gate
Στο /home/deploy/.ssh/authorized_keys, δέστε το δημόσιο κλειδί του πράκτορα σε αυτό το script:
restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-agent
Από το μηχάνημα όπου δουλεύει ο πράκτορας:
ssh deploy@preview.example.com up feature/login-form
# https://feature-login-form.preview.example.com
ssh deploy@preview.example.com down feature/login-form
Στη δοκιμή μας, το up έχτισε και δημοσίευσε το branch, ένα δεύτερο up μετά από νέο commit σέρβιρε τη νέα έκδοση, και το down αφαίρεσε το container και το site. Απόπειρες με cat /etc/shadow, up x; id, ονόματα branch όπως -x ή a/../b, ή διαδραστική σύνδεση με αυτό το κλειδί πήραν μόνο usage: up|down <branch>. Ένα όνομα branch πρέπει να ξεκινά με γράμμα ή ψηφίο και δεν μπορεί να περιέχει ...
Σύνδεση με τον πράκτορα
Με Claude Code ή Codex αρκεί μία γραμμή στο αρχείο οδηγιών του έργου:
Αφού κάνεις push ένα branch, τρέξε
ssh deploy@preview.example.com up <branch>και βάλε το URL που τυπώνεται στη σύνοψή σου. Τρέξεdownόταν το branch συγχωνευτεί ή εγκαταλειφθεί.
Αν το CI σας τρέχει ήδη σε κάθε push, καλέστε την ίδια εντολή από εκεί και κρατήστε το κλειδί εντελώς έξω από το περιβάλλον του πράκτορα. Είναι η αυστηρότερη επιλογή, και θα τη διαλέγαμε για οτιδήποτε πέρα από ένα προσωπικό έργο.
Τι να προσέχετε
- Δίσκος. Κάθε build αφήνει layers εικόνων. Ένα εβδομαδιαίο cron με
docker image prune -fτο κρατά υπό έλεγχο· τοdownσβήνει ήδη την εικόνα της ίδιας της προεπισκόπησης. - Branches που κανείς δεν κλείνει. Οι πράκτορες ανοίγουν περισσότερα branches από τους ανθρώπους. Ένα cron που απαριθμεί containers
preview-*παλαιότερα της εβδομάδας και τρέχειpreview-downσε αυτά σας γλιτώνει από είκοσι ξεχασμένες προεπισκοπήσεις. - Μυστικά. Οι προεπισκοπήσεις πρέπει να τρέχουν με δοκιμαστικά διαπιστευτήρια. Ο κωδικός basic auth κρατά τους ξένους έξω· δεν κάνει αποδεκτό να συνδέσετε μια προεπισκόπηση με τη βάση παραγωγής.
- Χρόνος build. Οι εικόνες χτίζονται στον server, οπότε ένα βαρύ build ανταγωνίζεται για CPU τις προεπισκοπήσεις που τρέχουν. Αν τα builds κρατούν λεπτά, χτίστε στο CI και κατεβάστε την εικόνα.
Ο ίδιος ο πράκτορας δεν χρειάζεται να ζει σε αυτόν τον server. Αν ζει, δείτε το VPS για Claude Code, και για να παραγγέλνει και να φροντίζει μόνος του αυτόν τον server, συνδέστε τον με τον MCP server της EQVPS. Το ίδιο μηχάνημα μπορεί να φιλοξενεί και το MCP gateway της ομάδας σας. Για μη αξιόπιστο κώδικα χωρίς καθόλου server υπάρχουν τα sandbox, και τα πακέτα με δική τους IPv4 ξεκινούν από $8 τον μήνα.
Σχόλια
Δεν υπάρχουν ακόμη σχόλια. Γίνετε ο πρώτος.