Héberger Archestack avec Docker Compose
Archestack tourne sur un seul serveur avec Docker Compose : PostgreSQL, l'API, le builder et l'application publiée, avec Caddy en frontal qui obtient lui-même chaque certificat. Un script prend la release d'Archestack la plus récente et, exécuté par cron chaque nuit, ou par un pipeline avec la version que vous choisissez, il tient le serveur à jour. C'est la manière la plus simple d'héberger Archestack. Quand un seul serveur ne suffit pas, ou qu'une mise à niveau ne doit pas coûter même un court redémarrage, Héberger sur AKS fait plutôt tourner plusieurs réplicas de l'API.
Ce qui tourne où
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 - Une image, deux shells. Le builder (
web) et l'application publiée (web-app) sont la même image web, démarrée dans un mode différent. - Caddy interroge l'API avant chaque nouveau certificat, si bien que l'adresse d'un espace de travail ou le domaine propre d'une entreprise en reçoit un la première fois que quelqu'un y accède, et rien d'autre n'en reçoit.
- L'API crée ses propres bases de données sur le PostgreSQL vide et les migre à son démarrage. Il n'y a aucun script à exécuter sur la base de données.
- Les données résident dans trois volumes Docker :
archestack_postgres,archestack_filespour les fichiers envoyés, etarchestack_backupspour les sauvegardes qu'Archestack fait lui-même. Un quatrième,archestack_nuget, garde les paquets NuGet qu'utilisent les scripts, pour qu'une mise à jour ne les télécharge pas à nouveau.
Avant de commencer
- Un serveur Linux avec Docker Engine et son plugin Compose. Quatre cœurs et 8 Go de mémoire sont un point de départ ; Capacité et dimensionnement donne les chiffres mesurés.
- Une zone DNS dans laquelle vous pouvez ajouter des enregistrements, avec les ports 80 et 443 du serveur joignables depuis internet (voie A), ou un reverse proxy sur le serveur qui sert déjà un nom d'hôte (voie B).
- Un serveur de mail par lequel Archestack peut envoyer, pour les invitations et les réinitialisations de mot de passe.
Le kit de démarrage
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. Renseigner .env
setup.sh crée .env à partir de .env.example, avec un mot de
passe de base de données que personne n'a besoin de connaître, lisible par vous seul :
#!/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" Renseignez ensuite chaque CHANGE-ME qu'il contient :
# .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_FILEchoisit la voie d'accès. Docker Compose le lit dans.env, si bien que chaque commandedocker composedu dossier s'exécute avec les bons fichiers. - Les mots de passe restent sur le serveur.
.envest le seul fichier qui les contient ; le.gitignoredu kit le tient à l'écart d'un dépôt. - Ne touchez plus à
POSTGRES_PASSWORDune fois la stack démarrée : la base de données garde le mot de passe avec lequel elle a été créée.
2. La stack
La base de données et l'API, que les deux voies d'accès partagent :
# 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 voie A ajoute le builder, l'application publiée et Caddy, sur des noms d'hôte qui leur sont propres :
# 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
}
Faites pointer editor et api sous votre zone vers le serveur, ainsi qu'un
enregistrement générique * pour les adresses propres des espaces de travail. Caddy
obtient ses certificats auprès de Let's Encrypt, donc les ports 80 et 443 doivent être joignables
depuis internet.
Sous un chemin d'un nom d'hôte que vous avez déjà
La voie B place Archestack sous un chemin d'un nom d'hôte qu'un reverse proxy sur le serveur sert
déjà, comme https://apps.acme.com/archestack/. Elle ne demande ni nouvel
enregistrement DNS, ni nouvelle adresse, ni certificat propre, et les utilisateurs continuent
d'utiliser l'adresse qu'ils connaissent déjà. Il n'y a pas de Caddy : l'API et le builder
n'écoutent que sur le serveur, et votre proxy leur envoie les chemins.
# 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" Ajoutez les chemins à votre proxy, pour 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;
} ou pour 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
} - Ne réécrivez pas le chemin. Archestack reçoit son préfixe et écrit ses propres adresses avec, si bien qu'il doit le voir.
- Un proxy qui est lui-même un conteneur atteint Archestack via un réseau Docker
partagé plutôt que via les ports du serveur : rattachez-le au réseau de la stack et utilisez
api:8080etweb:80. - Tenez les proxys de connexion à l'écart. Un proxy qui remplace l'en-tête
Authorizationcasse chaque appel à l'API : l'application publiée, les clés de la Data API, les jetons de déploiement, VS Code et MCP apportent tous le leur. - Le nom d'hôte est partagé. Les jetons de connexion sont conservés dans le stockage du navigateur pour cette adresse, que chaque application qui s'y trouve peut lire, si bien qu'un script injecté dans l'une d'elles atteint les sessions Archestack.
- Non disponibles par cette voie : les adresses des espaces de travail et les domaines propres des entreprises, qui ont besoin des certificats à la demande de Caddy.
3. Démarrer
# 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 Ouvrez immédiatement le builder et créez le premier admin. Tant que personne ne l'a fait, la première personne qui y accède devient l'admin.
La release la plus récente, automatiquement
update.sh détermine quelle release faire tourner, l'écrit dans .env et
démarre la stack sur celle-ci. La première exécution démarre tout ; chaque exécution suivante passe
à une release plus récente quand il y en a une :
#!/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 Depuis cron, chaque nuit :
# crontab -e: every night at 03:00, the newest release
0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1 - Un numéro de version, jamais
latest. La release la plus récente est transformée en son numéro, de la même manière que le fait le pipeline AKS, et chaque conteneur fait tourner ce numéro.docker compose psindique lequel tourne. -
ARCHESTACK_TRACKs'en tient à une seule ligne de versions :1.4prend la1.4.xla plus récente et ne passe jamais à1.5de lui-même. Réglez-le sur une version complète, comme1.4.23, pour rester sur celle-ci. - Une mise à niveau est un court redémarrage. Il n'y a qu'un seul conteneur d'API, si bien que pendant que le nouveau démarre et migre les bases de données, l'instance ne répond pas : quelques secondes en général, plus longtemps quand une release migre de nombreux espaces de travail. Lancez la mise à jour nocturne à une heure creuse.
- Revenir en arrière, c'est
./update.sh 1.4.22, puisARCHESTACK_TRACKréglé sur cette version, sinon l'exécution nocturne suivante avance de nouveau. Les bases de données restent telles que la version plus récente les a migrées ; préférez donc la prochaine release qui corrige le problème. - PostgreSQL et Caddy suivent leurs tags d'image (
postgres:16,caddy:2-alpine), si bien que la mise à jour apporte aussi leurs versions correctives, jamais une nouvelle version majeure.
Depuis un pipeline
Au lieu de cron, un pipeline sur Azure DevOps, GitHub Actions ou GitLab CI peut mettre à jour le
serveur, avec la version que vous choisissez. Il s'exécute depuis un dépôt qui contient le kit sous
archestack/ : il copie le kit sur le serveur via SSH et y exécute
update.sh. C'est alors la copie du dépôt qui tourne, si bien qu'une modification d'un
fichier Compose est relue et déployée comme n'importe quelle autre modification.
.env ne quitte jamais le serveur. Chaque environnement, du développement à la
production, est un serveur à part.
Sur chaque serveur, une seule fois, un utilisateur sous lequel le pipeline se connecte, propriétaire du dossier du 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)"
Décompressez le kit dans /opt/archestack et suivez les étapes 1 à 3 en tant que cet
utilisateur (sudo -iu archestack). Ensuite, dans le système de CI, créez un
environnement par serveur, archestack-dev d'abord, contenant :
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 Les trois pipelines exécutent 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" - Le serveur est vérifié avant tout envoi. Un serveur dont la clé d'hôte n'est pas
celle de
SSH_KNOWN_HOSTSest refusé, jamais accepté de confiance au premier contact. La ligne ci-dessus est tirée du propre fichier de clé du serveur, si bien qu'aucun réseau ne se trouve entre vous et lui. - Le groupe docker vaut root sur ce serveur, et la clé privée aussi. Créez une clé par serveur, et gardez-la uniquement dans les secrets du système de CI.
- Supprimez la ligne cron dès qu'un pipeline met à jour le serveur, sinon la nuit suivante le fait de nouveau passer à la release la plus récente. Une planification dans le système de CI peut la remplacer.
- Choisissez une version complète, comme
1.4.23, pour donner à la recette et à la production exactement ce qui tourne en 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) - Mise en place. Ajoutez
archestack/azure-pipelines.ymlcomme pipeline. Sous Pipelines, Library, le groupe de variablesarchestack-devcontient les trois paramètres, et la clé privée est le fichier sécuriséarchestack-dev.key. La première exécution vous demande d'autoriser le pipeline à utiliser les deux. - Approbations. Sous Pipelines, Environments, ajoutez une vérification Approvals
à
archestack-prdpour que la production attende une personne.
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 }} - Mise en place. Déplacez le fichier vers
.github/workflows/archestack.yml: GitHub n'exécute les workflows que depuis ce dossier. Sous Settings, Environments, les trois paramètres sont des variables etSSH_PRIVATE_KEYest un secret. Lancez-le sous Actions, Archestack, Run workflow. - Des relecteurs requis sur
archestack-prdfont attendre une personne à la production. Sur un dépôt privé, les environnements nécessitent GitHub Pro, Team ou 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 - Mise en place. Incluez le fichier depuis le
.gitlab-ci.ymlpropre au dépôt, ou créez-en un qui ne contient que ceci :include: - local: archestack/gitlab-ci.yml - Variables. Sous Settings, CI/CD, Variables, limitez la portée de chacune à
archestack-dev, avec la clé privée commeSSH_KEY_FILEde type File, et protégez-les, pour que seules les branches protégées les reçoivent. Lancez-le sous Build, Pipelines, New pipeline.
Données et sauvegardes
Archestack sauvegarde lui-même chaque espace de travail, dans le volume
archestack_backups (voir Database Backups). Ce
volume se trouve sur le même disque que tout le reste, donc copiez aussi les sauvegardes hors du
serveur :
# 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 Pièges
- Docker publie les ports en passant outre le pare-feu de l'hôte. Un port dans un
fichier Compose est ouvert même quand ufw dit le contraire. C'est pour cette raison que la
voie B se lie à
127.0.0.1; gardez-la ainsi. - Un
$dans une valeur de.envest lu comme une variable. Mettez cette valeur entre guillemets simples. - Une nouvelle version majeure de PostgreSQL (17) n'est pas un changement d'image :
ses fichiers diffèrent. Elle demande un
pg_dumpalldepuis l'ancienne et une restauration dans la nouvelle. -
docker compose down -vsupprime les volumes, et chaque espace de travail avec eux.docker compose downseul les conserve.
Pour aller plus loin
Héberger sur AKS couvre plusieurs réplicas de l'API, les mises à niveau sans redémarrage et un pipeline par environnement. Espaces de travail et hébergement explique comment une instance sert de nombreux espaces de travail, et Stratégie de déploiement couvre le déploiement de votre application dans ceux-ci à partir d'un dépôt.