Ospitare Archestack con Docker Compose
Archestack gira su un unico server con Docker Compose: PostgreSQL, l'API, il builder e l'app pubblicata, con Caddy davanti che ottiene da solo ogni certificato. Uno script prende la release più recente di Archestack, ed eseguito da cron ogni notte, o da una pipeline con la versione che scegli, mantiene aggiornato il server. È il modo più semplice di ospitare Archestack. Quando un server non basta, o un aggiornamento non può costare nemmeno un breve riavvio, Hosting su AKS esegue invece più repliche dell'API.
Cosa gira dove
this server, ports 80 and 443
|
Caddy ---- asks the API before every new certificate
+-------------------+--------------------------+
editor.<zone> api.<zone> <workspace>.<zone>, companies' own domains
| | |
web api web-app
|
postgres volumes: postgres, files, backups, nuget - Un'immagine, due shell. Il builder (
web) e l'app pubblicata (web-app) sono la stessa immagine web, avviata in una modalità diversa. - Caddy chiede all'API prima di ogni nuovo certificato, così l'indirizzo di un workspace o il dominio proprio di un'azienda ne riceve uno la prima volta che qualcuno lo visita, e nient'altro lo riceve.
- L'API crea da sola i propri database sul PostgreSQL vuoto e li migra all'avvio. Non c'è nessuno script da eseguire sul database.
- I dati stanno in tre volumi Docker:
archestack_postgres,archestack_filesper gli upload, earchestack_backupsper i backup propri di Archestack. Un quarto,archestack_nuget, tiene i pacchetti NuGet che usano gli script, così un aggiornamento non li scarica di nuovo.
Prima di iniziare
- Un server Linux con Docker Engine e il suo plugin Compose. Quattro core e 8 GB di memoria sono un punto di partenza; Capacità e dimensionamento riporta le cifre misurate.
- Una zona DNS a cui puoi aggiungere record, con le porte 80 e 443 del server raggiungibili da internet (via A), oppure un reverse proxy sul server che serve già un hostname (via B).
- Un server di posta tramite cui Archestack può inviare, per gli inviti e il reset delle password.
Lo starter kit
setup.sh makes .env, with a generated database password
.env.example what .env holds: fill in every CHANGE-ME
update.sh the newest release, pulled and started; nightly from cron
resolve-version.sh newest, 1.4 or 1.4.23, made one version number
compose.yaml PostgreSQL and the API, for both ways in
compose.caddy.yaml way A: the builder, the published app and Caddy
Caddyfile way A: hostnames and certificates
compose.path.yaml way B: the builder under a path, for your own proxy
proxy/nginx.conf way B: what to add to an nginx you already run
proxy/Caddyfile way B: the same for a Caddy you already run
deploy.sh from a pipeline: copies the kit to the server, runs update.sh there
azure-pipelines.yml that pipeline on Azure DevOps
github-actions.yml on GitHub Actions: move it to .github/workflows/archestack.yml
gitlab-ci.yml on GitLab CI: include it from the repository's .gitlab-ci.yml 1. Compila .env
setup.sh crea .env a partire da .env.example, con una password
del database che nessuno ha bisogno di conoscere, leggibile solo da te:
#!/usr/bin/env bash
# setup.sh: makes .env from .env.example, with a database password nobody has to know.
# ./setup.sh
set -euo pipefail
cd "$(dirname "$0")"
if [ -e .env ]; then echo ".env already exists; leaving it as it is" >&2; exit 1; fi
# Letters and digits only: a ';' would end the connection string, a '$' would be read as a variable.
# Read in whole pieces: a reader that stops early would kill the writer, and pipefail the script.
password=""
while [ ${#password} -lt 40 ]; do
password+=$(head -c 64 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9')
done
password=${password:0:40}
umask 077
sed "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$password/" .env.example > .env
echo "Made .env, readable by you only. Fill in every CHANGE-ME in it, then run ./update.sh" Poi compila ogni CHANGE-ME che contiene:
# .env: this server's settings. ./setup.sh makes it from .env.example; then fill in every CHANGE-ME.
# It holds passwords: keep it out of git (the kit's .gitignore does) and readable by you only.
# A value with a $ in it goes in single quotes, such as SMTP_PASSWORD='pa$$word'.
# Way A (hostnames of its own, Caddy) or way B (a path of a hostname your proxy already serves).
COMPOSE_PATH_SEPARATOR=:
COMPOSE_FILE=compose.yaml:compose.caddy.yaml
#COMPOSE_FILE=compose.yaml:compose.path.yaml
# The release running now, written by ./update.sh. ARCHESTACK_TRACK keeps updates to one line,
# such as 1.4; empty for the newest release.
ARCHESTACK_VERSION=
ARCHESTACK_TRACK=
# Made by ./setup.sh. Never change it here alone: the database keeps the one it was created with.
POSTGRES_PASSWORD=
# Way A: your zone. The builder at editor.<zone>, the API at api.<zone>, each workspace at
# <label>.<zone>. Empty both for way B.
ZONE=CHANGE-ME
ACME_EMAIL=CHANGE-ME
# Way B: the hostname your proxy already serves, the path Archestack goes under, and the ports
# on this server your proxy sends it to.
SHARED_HOST=
BASE_PATH=/archestack
API_PORT=8080
WEB_PORT=8081
# The mail server. Empty all five when the instance sends no mail.
SMTP_HOST=CHANGE-ME
SMTP_PORT=587
SMTP_USERNAME=CHANGE-ME
SMTP_PASSWORD=CHANGE-ME
SMTP_FROM=CHANGE-ME -
COMPOSE_FILEsceglie la via d'accesso. Docker Compose lo legge da.env, così ogni comandodocker composenella cartella esegue i file giusti. - Le password restano sul server.
.envè l'unico file che le contiene; il.gitignoredel kit lo tiene fuori da un repository. - Non toccare
POSTGRES_PASSWORDuna volta avviato lo stack: il database mantiene la password con cui è stato creato.
2. Lo stack
Il database e l'API, che entrambe le vie d'accesso condividono:
# compose.yaml: the database and the API, the part both ways in share. .env picks the rest
# (COMPOSE_FILE), so `docker compose up -d` runs the whole stack. Start it with ./update.sh.
name: archestack
services:
postgres:
image: postgres:16
restart: unless-stopped
# PostgreSQL keeps statistics for every database in shared memory, and every workspace owns
# two. Docker's default of 64 MB runs out at about a thousand workspaces.
shm_size: 1g
environment:
POSTGRES_USER: archestack
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?run ./setup.sh first}
POSTGRES_DB: postgres
volumes:
- postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U archestack -d postgres"]
interval: 5s
retries: 30
api:
image: nickpeelman/omnicore-api:${ARCHESTACK_VERSION:?run ./update.sh, which picks the version}
restart: unless-stopped
depends_on:
postgres: { condition: service_healthy }
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
# The API makes these databases itself on the empty server, and migrates them as it starts.
ConnectionStrings__DefaultConnection: Host=postgres;Database=omnicore-db;Username=archestack;Password=${POSTGRES_PASSWORD};SSL Mode=Disable
ConnectionStrings__Core: Host=postgres;Database=omnicore-core;Username=archestack;Password=${POSTGRES_PASSWORD};SSL Mode=Disable
ConnectionStrings__Identity: Host=postgres;Database=omnicore-identity;Username=archestack;Password=${POSTGRES_PASSWORD};SSL Mode=Disable
ConnectionStrings__Keycloak: ""
Database__CreateMissing: "true"
Identity__Provider: Builtin
Identity__Issuer: omnicore
Identity__ImportFromKeycloak: "false"
Identity__AllowCompanySignup: "false"
Smtp__Host: ${SMTP_HOST:-}
Smtp__Port: ${SMTP_PORT:-587}
Smtp__UseSsl: "true"
Smtp__Username: ${SMTP_USERNAME:-}
Smtp__Password: ${SMTP_PASSWORD:-}
Smtp__From: ${SMTP_FROM:-}
Smtp__FromName: Archestack
# The proxy in front is on a Docker network; the rate limiter reads the visitor's address from it.
ForwardedHeaders__TrustedProxies: 127.0.0.0/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7
Backup__Directory: /backups
Files__Directory: /files
# The NuGet packages scripts use, on a volume of the API's own, so an update does not
# download them again.
Scripting__NuGet__Directory: /nuget
volumes:
- files:/files
- backups:/backups
- nuget:/nuget
volumes:
postgres:
files:
backups:
nuget: La via A aggiunge il builder, l'app pubblicata e Caddy, su hostname tutti loro:
# compose.caddy.yaml: way A. Hostnames of its own (editor.<zone>, api.<zone>, <workspace>.<zone>),
# behind Caddy, which gets every certificate by itself. Needs ports 80 and 443 and DNS records.
services:
api:
environment:
Identity__AppBaseUrl: https://editor.${ZONE:?fill in ZONE in .env}
# Turns on an address per workspace, and companies' own domains.
Tenancy__HostSuffix: ${ZONE}
# The builder.
web:
image: nickpeelman/omnicore-web:${ARCHESTACK_VERSION:?run ./update.sh}
restart: unless-stopped
environment:
VITE_API_URL: https://api.${ZONE}
VITE_UI_MODE: dashboard
API_INTERNAL_URL: http://api:8080
# The published app, at every workspace's address. Same image, other shell.
web-app:
image: nickpeelman/omnicore-web:${ARCHESTACK_VERSION:?run ./update.sh}
restart: unless-stopped
environment:
VITE_API_URL: https://api.${ZONE}
VITE_UI_MODE: kiosk
VITE_DASHBOARD_URL: https://editor.${ZONE}
API_INTERNAL_URL: http://api:8080
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
ZONE: ${ZONE}
ACME_EMAIL: ${ACME_EMAIL:?fill in ACME_EMAIL in .env}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config: # Caddyfile: way A. {$ZONE} and {$ACME_EMAIL} come from .env, through compose.caddy.yaml.
{
email {$ACME_EMAIL}
# A workspace address or a company's own domain gets a certificate only when the API says
# the hostname belongs to a live workspace.
on_demand_tls {
ask http://api:8080/api/v1/tenants/verify-hostname
}
}
(common) {
encode zstd gzip
header {
# No includeSubDomains: on a company's own root domain it would force HTTPS on all
# their other subdomains for a year.
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}
editor.{$ZONE} {
import common
reverse_proxy web:80
}
api.{$ZONE} {
import common
reverse_proxy api:8080 {
# Live updates stream; nothing waits for a buffer to fill.
flush_interval -1
}
}
# Every other hostname: a workspace's address or a company's own domain, the published app.
https:// {
import common
tls {
on_demand
}
reverse_proxy web-app:80
}
Fai puntare al server editor e api nella tua zona, più un wildcard
* per gli indirizzi propri dei workspace. Caddy ottiene i suoi certificati da Let's
Encrypt, quindi le porte 80 e 443 devono essere raggiungibili da internet.
Sotto un percorso di un hostname che hai già
La via B mette Archestack sotto un percorso di un hostname che un reverse proxy sul server serve
già, come https://apps.acme.com/archestack/. Non servono un nuovo record DNS, un nuovo
indirizzo né un certificato proprio, e le persone continuano a usare l'indirizzo che già conoscono.
Non c'è Caddy: l'API e il builder sono in ascolto solo sul server, e il tuo proxy invia loro i
percorsi.
# compose.path.yaml: way B. Under a path of a hostname the reverse proxy you already run serves,
# such as https://apps.acme.com/archestack/. No DNS record, address or certificate of its own.
# Both containers listen on this server only; your proxy sends the paths to them (see the guide).
services:
api:
environment:
# Requests arrive with the prefix on; the API writes its own addresses with it.
Http__PathBase: ${BASE_PATH:?fill in BASE_PATH in .env}
Identity__AppBaseUrl: https://${SHARED_HOST:?fill in SHARED_HOST in .env}${BASE_PATH}
ports:
- "127.0.0.1:${API_PORT:-8080}:8080"
# The builder, which also serves the published app under <path>/app.
web:
image: nickpeelman/omnicore-web:${ARCHESTACK_VERSION:?run ./update.sh}
restart: unless-stopped
environment:
VITE_API_URL: ""
BASE_PATH: ${BASE_PATH}
VITE_UI_MODE: dashboard
API_INTERNAL_URL: http://api:8080
ports:
- "127.0.0.1:${WEB_PORT:-8081}:80" Aggiungi i percorsi al tuo proxy, per nginx:
# proxy/nginx.conf: way B, for an nginx that already serves the hostname. Paste these into its
# server block for that hostname, and reload nginx. The ports are API_PORT and WEB_PORT in .env.
# No path after the address in proxy_pass: the prefix goes through as it is, and Archestack needs it.
client_max_body_size 1024m; # package imports and file uploads
location /archestack/api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s; # backups and imports
}
location /archestack/hubs/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1; # live updates are WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
location = /archestack {
return 301 /archestack/;
}
location /archestack/ {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
} oppure per Caddy:
# proxy/Caddyfile: way B, for a Caddy that already serves the hostname. Add these lines inside its
# site block for that hostname, and reload Caddy. The ports are API_PORT and WEB_PORT in .env.
# handle keeps the path as it is, which Archestack needs; WebSockets and large uploads just work.
apps.acme.com {
handle /archestack/api/* {
reverse_proxy 127.0.0.1:8080
}
handle /archestack/hubs/* {
reverse_proxy 127.0.0.1:8080
}
redir /archestack /archestack/
handle /archestack/* {
reverse_proxy 127.0.0.1:8081
}
# ... what this hostname already serves
} - Non riscrivere il percorso. Ad Archestack viene comunicato il suo prefisso e scrive i propri indirizzi con esso, quindi deve vederlo.
- Un proxy che è esso stesso un container raggiunge Archestack tramite una rete
Docker condivisa invece che tramite le porte del server: collegalo alla rete dello stack e usa
api:8080eweb:80. - Tienine fuori i proxy di accesso. Uno che sostituisce l'header
Authorizationrompe ogni chiamata all'API: l'app pubblicata, le chiavi della Data API, i token di deploy, VS Code e MCP portano tutti il proprio. - L'hostname è condiviso. I token di accesso stanno nello storage del browser per quell'indirizzo, che ogni applicazione su di esso può leggere, quindi uno script iniettato in una qualsiasi di esse raggiunge le sessioni di Archestack.
- Non disponibili in questo modo: gli indirizzi dei workspace e i domini propri delle aziende, che hanno bisogno dei certificati su richiesta di Caddy.
3. Avvialo
# On the server, in the folder the kit was unzipped to, such as /opt/archestack
./setup.sh # makes .env; then fill in every CHANGE-ME in it
./update.sh # pulls the newest release and starts everything
docker compose ps # every container running Apri subito il builder e crea il primo admin. Finché nessuno lo fa, chi lo raggiunge per primo diventa l'admin.
La release più recente, in automatico
update.sh stabilisce quale release eseguire, la scrive in .env e avvia lo
stack su di essa. La prima esecuzione avvia tutto; ogni esecuzione successiva passa a una release
più recente quando ce n'è una:
#!/usr/bin/env bash
# update.sh: start Archestack on the newest release, or move it to one. Every night from cron:
# 0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1
# By hand:
# ./update.sh the newest release (within ARCHESTACK_TRACK, when .env sets one)
# ./update.sh 1.4.22 exactly that one, also to go back
set -euo pipefail
cd "$(dirname "$0")"
[ -f .env ] || { echo "No .env yet: run ./setup.sh first" >&2; exit 1; }
if grep -v '^[[:space:]]*#' .env | grep -q CHANGE-ME; then echo "Fill in every CHANGE-ME in .env first" >&2; exit 1; fi
setting() { grep -E "^$1=" .env | tail -n 1 | cut -d= -f2- | tr -d "\"'"; }
# 1. Which version: a number, worked out once and written to .env, so every container runs it.
current=$(setting ARCHESTACK_VERSION)
track=$(setting ARCHESTACK_TRACK)
ARCHESTACK_VERSION=${1:-${track:-newest}} OUT_DIR=. bash resolve-version.sh > /dev/null
version=$(cat version)
if [ "$version" = "$current" ]; then
echo "$(date -u +%FT%TZ) already on $version"
else
echo "$(date -u +%FT%TZ) ${current:-nothing} -> $version"
sed -i "s/^ARCHESTACK_VERSION=.*/ARCHESTACK_VERSION=$version/" .env
fi
# 2. Pull, then recreate whatever changed. Containers whose image and settings are the same keep running.
docker compose pull --quiet
docker compose up -d --remove-orphans
# 3. Wait for the API: it migrates every workspace before it answers, which takes a while with many.
for _ in $(seq 180); do
if docker compose exec -T web wget -q -O /dev/null http://api:8080/health 2> /dev/null; then
echo "$(date -u +%FT%TZ) Archestack $version is up"
exit 0
fi
sleep 5
done
echo "$(date -u +%FT%TZ) the API did not answer within 15 minutes: docker compose logs api" >&2
exit 1 Da cron, ogni notte:
# crontab -e: every night at 03:00, the newest release
0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1 - Un numero di versione, mai
latest. La release più recente viene trasformata nel suo numero, nello stesso modo in cui lo fa la pipeline AKS, e ogni container esegue quel numero.docker compose psindica quale gira. -
ARCHESTACK_TRACKsegue una sola linea:1.4prende la1.4.xpiù recente e non passa mai da sola a1.5. Impostalo su una versione completa, come1.4.23, per restare su quella. - Un aggiornamento è un breve riavvio. C'è un solo container dell'API, quindi mentre quello nuovo si avvia e migra i database, l'istanza non risponde: di solito questione di secondi, di più quando una release migra molti workspace. Esegui l'aggiornamento notturno in un'ora tranquilla.
- Tornare indietro è
./update.sh 1.4.22, poiARCHESTACK_TRACKimpostato su quella versione, altrimenti la successiva esecuzione notturna va di nuovo avanti. I database restano come li ha migrati la versione più nuova, quindi preferisci la release successiva che risolve il problema. - PostgreSQL e Caddy seguono i tag delle loro immagini (
postgres:16,caddy:2-alpine), quindi l'aggiornamento porta anche le loro patch release, mai una nuova versione major.
Da una pipeline
Invece di cron, una pipeline su Azure DevOps, GitHub Actions o GitLab CI può aggiornare il server,
con la versione che scegli. Gira da un repository che contiene il kit come archestack/:
copia il kit sul server tramite SSH ed esegue lì update.sh. A quel punto è la copia del
repository a girare, quindi una modifica a un file Compose viene revisionata e distribuita come
qualsiasi altra modifica. .env non lascia mai il server. Ogni ambiente, dallo sviluppo
alla produzione, è un server a sé.
Su ogni server, una volta sola, un utente con cui accede la pipeline, proprietario della cartella del kit:
# On the server, as root, once
useradd -m -s /bin/bash -G docker archestack # the user the pipeline signs in as
install -d -o archestack -g archestack /opt/archestack # the kit's folder, its own
# A key pair for the pipeline: the public half may sign in as archestack
ssh-keygen -t ed25519 -N '' -C archestack-dev -f /root/archestack-dev
install -d -m 700 -o archestack -g archestack /home/archestack/.ssh
install -m 600 -o archestack -g archestack /root/archestack-dev.pub /home/archestack/.ssh/authorized_keys
cat /root/archestack-dev # the private half: into the CI system, then
rm /root/archestack-dev /root/archestack-dev.pub
# SSH_KNOWN_HOSTS, made from the server's own host key, under the name the pipeline uses
echo "archestack.example.com $(cut -d' ' -f1,2 /etc/ssh/ssh_host_ed25519_key.pub)"
Estrai il kit in /opt/archestack e segui i passi da 1 a 3 come quell'utente
(sudo -iu archestack). Poi, nel sistema CI, crea un ambiente per server, a cominciare
da archestack-dev, che contenga:
SSH_HOST archestack.example.com
SSH_USER archestack
SSH_KNOWN_HOSTS the line the last command printed
the private key Azure DevOps: the secure file archestack-<env>.key
GitHub Actions: the secret SSH_PRIVATE_KEY
GitLab CI: SSH_KEY_FILE, a variable of type File Tutte e tre le pipeline eseguono deploy.sh:
#!/usr/bin/env bash
# archestack/deploy.sh: what the pipelines run (azure-pipelines.yml, github-actions.yml, gitlab-ci.yml).
# Copies this folder to the server, leaving the server's .env as it is, then runs update.sh there.
# SSH_HOST the server, such as archestack.example.com
# SSH_USER the user it signs in as: in the docker group, owning the folder
# SSH_KNOWN_HOSTS the server's host key, as a known_hosts line
# SSH_PRIVATE_KEY the private key, or SSH_KEY_FILE the path of a file holding it
# ARCHESTACK_VERSION newest, 1.4 for the newest 1.4.x, or 1.4.23; empty for what .env says
# ARCHESTACK_DIR the folder on the server, /opt/archestack unless set
set -euo pipefail
cd "$(dirname "$0")"
# An Azure DevOps variable that does not exist arrives as its own name, $(NAME).
missing() { case "${!1:-}" in "" | '$('*) return 0 ;; *) return 1 ;; esac; }
for name in SSH_HOST SSH_USER SSH_KNOWN_HOSTS; do
if missing "$name"; then echo "Set $name for this environment" >&2; exit 1; fi
done
if missing SSH_PRIVATE_KEY && missing SSH_KEY_FILE; then
echo "Set SSH_PRIVATE_KEY (or SSH_KEY_FILE) for this environment" >&2
exit 1
fi
version=${ARCHESTACK_VERSION:-}
if [[ ! $version =~ ^[A-Za-z0-9.]*$ ]]; then echo "Not a version: $version" >&2; exit 1; fi
dir=${ARCHESTACK_DIR:-/opt/archestack}
# The key and the server's host key, in files only this job reads. A server whose key is not the one
# in SSH_KNOWN_HOSTS is refused, never trusted on first sight.
ssh_dir=$(mktemp -d)
trap 'rm -rf "$ssh_dir"' EXIT
if missing SSH_PRIVATE_KEY; then cat "$SSH_KEY_FILE"; else printf '%s\n' "$SSH_PRIVATE_KEY"; fi \
| tr -d '\r' > "$ssh_dir/key"
chmod 600 "$ssh_dir/key"
printf '%s\n' "$SSH_KNOWN_HOSTS" | tr -d '\r' > "$ssh_dir/known_hosts"
remote=(ssh -i "$ssh_dir/key" -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes
-o UserKnownHostsFile="$ssh_dir/known_hosts" "$SSH_USER@$SSH_HOST")
# 1. The kit as the repository has it. Never .env, which holds the passwords and stays on the server.
tar -czf - --exclude=.env --exclude=version --exclude=update.log . \
| "${remote[@]}" "mkdir -p '$dir' && tar -xzf - -C '$dir' && chmod +x '$dir'/*.sh"
# 2. The release, pulled and started. update.sh waits until the API answers.
"${remote[@]}" "'$dir/update.sh' $version" - Il server viene verificato prima di inviare qualsiasi cosa. Uno la cui host key
non è quella in
SSH_KNOWN_HOSTSviene rifiutato, mai considerato attendibile al primo contatto. La riga qui sopra è ricavata dal file della chiave del server stesso, quindi tra te e il server non c'è nessuna rete in mezzo. - Il gruppo docker vale quanto root su quel server, e lo stesso vale per la chiave privata. Crea una chiave per server, e tienila solo nei segreti del sistema CI.
- Rimuovi la riga di cron una volta che una pipeline aggiorna il server, altrimenti la notte successiva lo porta di nuovo alla release più recente. Una pianificazione nel sistema CI può prenderne il posto.
- Scegli una versione completa, come
1.4.23, per dare a collaudo e produzione esattamente ciò che gira in test.
Azure DevOps
# archestack/azure-pipelines.yml: Archestack on its server with Docker Compose, from Azure DevOps.
# Add it as a pipeline, run it by hand, and pick the environment and the Archestack version.
trigger: none
parameters:
- name: environment
displayName: Environment
type: string
default: dev
values: [dev, tst, acc, prd]
- name: version
displayName: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
type: string
default: newest
variables:
# One variable group per environment, named archestack-<environment>, holding:
# SSH_HOST the server, such as archestack.example.com
# SSH_USER the user the pipeline signs in as
# SSH_KNOWN_HOSTS the server's host key, as a known_hosts line
# and one secure file (Pipelines, Library, Secure files), archestack-<environment>.key: the private key.
- group: archestack-${{ parameters.environment }}
stages:
- stage: deploy
displayName: Deploy ${{ parameters.environment }}
jobs:
- deployment: deploy
displayName: Update the server
pool:
vmImage: ubuntu-latest
# Add an Approvals check to archestack-prd (Pipelines, Environments) to make production wait.
environment: archestack-${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: DownloadSecureFile@1
name: key
displayName: The SSH key
inputs:
secureFile: archestack-${{ parameters.environment }}.key
- bash: bash archestack/deploy.sh
displayName: Copy the kit and update
env:
ARCHESTACK_VERSION: ${{ parameters.version }}
SSH_HOST: $(SSH_HOST)
SSH_USER: $(SSH_USER)
SSH_KNOWN_HOSTS: $(SSH_KNOWN_HOSTS)
SSH_KEY_FILE: $(key.secureFilePath) - Configurazione. Aggiungi
archestack/azure-pipelines.ymlcome pipeline. In Pipelines, Library, il gruppo di variabiliarchestack-devcontiene le tre impostazioni, e la chiave privata è il secure filearchestack-dev.key. La prima esecuzione ti chiede di consentire alla pipeline di usarli entrambi. - Approvazioni. In Pipelines, Environments, aggiungi un controllo Approvals a
archestack-prdperché la produzione aspetti una persona.
GitHub Actions
# archestack/github-actions.yml: Archestack on its server with Docker Compose, from GitHub Actions.
# GitHub runs workflows from one folder only: move this file to .github/workflows/archestack.yml.
# Run it under Actions, Archestack, Run workflow, and pick the environment and the version.
name: Archestack
on:
workflow_dispatch:
inputs:
environment:
description: Environment
type: choice
options: [dev, tst, acc, prd]
default: dev
version:
description: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
default: newest
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
# One GitHub environment per server, named archestack-<environment>, holding the variables
# SSH_HOST the server, such as archestack.example.com
# SSH_USER the user the pipeline signs in as
# SSH_KNOWN_HOSTS the server's host key, as a known_hosts line
# and the secret SSH_PRIVATE_KEY, the private key.
environment: archestack-${{ inputs.environment }}
concurrency: archestack-${{ inputs.environment }} # one update per server at a time
steps:
- uses: actions/checkout@v4
- name: Copy the kit and update
run: bash archestack/deploy.sh
env:
ARCHESTACK_VERSION: ${{ inputs.version }}
SSH_HOST: ${{ vars.SSH_HOST }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_KNOWN_HOSTS: ${{ vars.SSH_KNOWN_HOSTS }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - Configurazione. Sposta il file in
.github/workflows/archestack.yml: GitHub esegue i workflow solo da quella cartella. In Settings, Environments, le tre impostazioni sono variabili eSSH_PRIVATE_KEYè un segreto. Eseguilo in Actions, Archestack, Run workflow. - I revisori obbligatori su
archestack-prdfanno sì che la produzione aspetti una persona. Su un repository privato, gli ambienti richiedono GitHub Pro, Team o Enterprise.
GitLab CI
# archestack/gitlab-ci.yml: Archestack on its server with Docker Compose, from GitLab CI. Include it
# from the repository's own .gitlab-ci.yml (a file holding just these two lines when there is none):
# include:
# - local: archestack/gitlab-ci.yml
# Run it under Build, Pipelines, New pipeline, and pick the environment and the version.
variables:
ARCHESTACK_ENVIRONMENT:
value: dev
options: [dev, tst, acc, prd]
description: Environment
ARCHESTACK_VERSION:
value: newest
description: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
archestack:
stage: deploy
image: alpine:3.22
rules:
- if: $CI_PIPELINE_SOURCE == "web"
# Variables scoped to this environment (Settings, CI/CD, Variables), all protected:
# SSH_HOST the server, such as archestack.example.com
# SSH_USER the user the pipeline signs in as
# SSH_KNOWN_HOSTS the server's host key, as a known_hosts line
# SSH_KEY_FILE type File: the private key
environment:
name: archestack-$ARCHESTACK_ENVIRONMENT
resource_group: archestack-$ARCHESTACK_ENVIRONMENT # one update per server at a time
before_script:
- apk add --no-cache bash openssh-client tar
script:
- bash archestack/deploy.sh - Configurazione. Includi il file dal
.gitlab-ci.ymldel repository stesso, oppure creane uno che contenga solo questo:include: - local: archestack/gitlab-ci.yml - Variabili. In Settings, CI/CD, Variables, limita ciascuna all'ambiente
archestack-dev, con la chiave privata comeSSH_KEY_FILEdi tipo File, e proteggile, così solo i branch protetti le ricevono. Eseguila in Build, Pipelines, New pipeline.
Dati e backup
Archestack fa da solo il backup di ogni workspace, nel volume archestack_backups (vedi
Database Backups). Quel volume sta sullo stesso disco di tutto il
resto, quindi copia i backup anche fuori dal server:
# Every database on the server, as one file, then copied off the server
docker compose exec -T postgres pg_dumpall -U archestack | gzip > archestack-$(date +%F).sql.gz
# Workspace files and Archestack's own backups, from their volumes
docker run --rm -v archestack_files:/files -v archestack_backups:/backups -v "$PWD":/out alpine \
tar czf /out/archestack-files-$(date +%F).tar.gz /files /backups Trappole
- Docker pubblica le porte scavalcando il firewall dell'host. Una porta in un file
Compose è aperta anche quando ufw dice il contrario. Per questo la via B fa il bind su
127.0.0.1; lasciala così. - Un
$in un valore di.envviene letto come una variabile. Metti quel valore tra virgolette singole. - Una nuova versione major di PostgreSQL (17) non è un cambio di immagine: i suoi
file sono diversi. Richiede un
pg_dumpallda quella vecchia e un ripristino in quella nuova. -
docker compose down -velimina i volumi, e con essi ogni workspace.docker compose downda solo li mantiene.
Dove andare dopo
Hosting su AKS copre più repliche dell'API, aggiornamenti senza riavvio e una pipeline per ambiente. Workspace e hosting spiega come un'istanza serve molti workspace, e Strategia di deployment descrive come fare il deploy della tua app nei workspace da un repository.