Archestack hosten met Docker Compose
Archestack draait op één server met Docker Compose: PostgreSQL, de API, de builder en de gepubliceerde app, met Caddy ervoor, die elk certificaat zelf ophaalt. Een script neemt de nieuwste Archestack-release, en als het elke nacht vanuit cron draait, of vanuit een pipeline met de versie die je kiest, houdt het de server up-to-date. Het is de eenvoudigste manier om Archestack te hosten. Wanneer één server niet genoeg is, of een upgrade zelfs geen korte herstart mag kosten, draait Hosten op AKS in plaats daarvan meerdere API-replica's.
Wat waar draait
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 - Eén image, twee shells. De builder (
web) en de gepubliceerde app (web-app) zijn dezelfde web-image, in een andere modus gestart. - Caddy raadpleegt de API vóór elk nieuw certificaat, dus het adres van een werkruimte of het eigen domein van een bedrijf krijgt er een de eerste keer dat iemand het bezoekt, en niets anders krijgt er een.
- De API maakt zijn eigen databases aan op de lege PostgreSQL en migreert ze bij het starten. Er is geen script dat je tegen de database moet draaien.
- Data staat in drie Docker-volumes:
archestack_postgres,archestack_filesvoor uploads, enarchestack_backupsvoor de eigen back-ups van Archestack. Een vierde,archestack_nuget, bewaart de NuGet-packages die scripts gebruiken, zodat een update ze niet opnieuw downloadt.
Voordat je begint
- Een Linux-server met Docker Engine en de Compose-plugin ervan. Vier cores en 8 GB geheugen zijn een startpunt; Capaciteit en dimensionering geeft de gemeten cijfers.
- Een DNS-zone waaraan je records kunt toevoegen, met poort 80 en 443 van de server bereikbaar vanaf internet (manier A), of een reverse proxy op de server die al een hostnaam bedient (manier B).
- Een mailserver waarlangs Archestack mag versturen, voor uitnodigingen en wachtwoordresets.
De starterkit
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. Vul .env in
setup.sh maakt .env aan uit .env.example, met een
databasewachtwoord dat niemand hoeft te kennen, en alleen door jou leesbaar:
#!/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" Vul daarna elke CHANGE-ME erin in:
# .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_FILEkiest de manier om binnen te komen. Docker Compose leest hem uit.env, dus elkdocker compose-commando in de map draait de juiste bestanden. - Wachtwoorden blijven op de server.
.envis het enige bestand dat ze bevat; de.gitignorevan de kit houdt het uit een repository. - Laat
POSTGRES_PASSWORDmet rust zodra de stack is gestart: de database houdt het wachtwoord waarmee hij is aangemaakt.
2. De stack
De database en de API, die beide manieren om binnen te komen delen:
# 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: Manier A voegt de builder, de gepubliceerde app en Caddy toe, op eigen hostnamen:
# 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
}
Laat editor en api onder je zone naar de server wijzen, plus een wildcard
* voor de eigen adressen van de werkruimtes. Caddy haalt zijn certificaten bij Let's
Encrypt, dus poort 80 en 443 moeten vanaf internet bereikbaar zijn.
Onder een pad van een hostnaam die je al hebt
Manier B zet Archestack onder een pad van een hostnaam die een reverse proxy op de server al
bedient, zoals https://apps.acme.com/archestack/. Er is geen nieuw DNS-record, geen
nieuw adres en geen eigen certificaat voor nodig, en mensen blijven het adres gebruiken dat ze al
kennen. Er is geen Caddy: de API en de builder luisteren alleen op de server, en je proxy stuurt de
paden naar ze door.
# 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" Voeg de paden toe aan je proxy, voor 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;
} of voor 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
} - Herschrijf het pad niet. Archestack krijgt zijn prefix te horen en schrijft zijn eigen adressen ermee, dus het moet die prefix zien.
- Een proxy die zelf een container is, bereikt Archestack via een gedeeld
Docker-netwerk in plaats van via de poorten van de server: verbind hem met het netwerk van de stack
en gebruik
api:8080enweb:80. - Houd aanmeldproxy's erbuiten. Een proxy die de
Authorization-header vervangt, breekt elke API-aanroep: de gepubliceerde app, Data API-sleutels, deploytokens, VS Code en MCP brengen allemaal hun eigen header mee. - De hostnaam is gedeeld. Aanmeldtokens staan in de opslag van de browser voor dat adres, die elke applicatie op dat adres kan lezen, dus een script dat in een van die applicaties wordt geïnjecteerd, bereikt Archestack-sessies.
- Niet beschikbaar op deze manier: adressen van werkruimtes en de eigen domeinen van bedrijven, die de certificaten op aanvraag van Caddy nodig hebben.
3. Start het
# 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 Open meteen de builder en maak de eerste admin aan. Tot iemand dat doet, wordt wie de builder als eerste bereikt de admin.
De nieuwste release, automatisch
update.sh bepaalt welke release er moet draaien, schrijft die naar .env en
start de stack erop. De eerste run start alles; elke latere run stapt over naar een nieuwere release
wanneer die er is:
#!/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 Vanuit cron, elke nacht:
# crontab -e: every night at 03:00, the newest release
0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1 - Een versienummer, nooit
latest. De nieuwste release wordt omgezet in zijn nummer, op dezelfde manier als de AKS-pipeline dat doet, en elke container draait dat nummer.docker compose pslaat zien welk nummer er draait. -
ARCHESTACK_TRACKhoudt zich aan één lijn:1.4neemt de nieuwste1.4.xen stapt nooit vanzelf over naar1.5. Zet hem op een volledige versie, zoals1.4.23, om op die versie te blijven. - Een upgrade is een korte herstart. Er is één API-container, dus terwijl de nieuwe start en de databases migreert, antwoordt de instantie niet: meestal seconden, langer wanneer een release veel werkruimtes migreert. Draai de nachtelijke update op een rustig uur.
- Teruggaan is
./update.sh 1.4.22, en daarnaARCHESTACK_TRACKdaarop zetten, anders gaat de volgende nachtelijke run weer vooruit. De databases blijven zoals de nieuwere versie ze heeft gemigreerd, dus kies liever de volgende release die het probleem oplost. - PostgreSQL en Caddy volgen hun imagetags (
postgres:16,caddy:2-alpine), dus de update brengt ook hun patchreleases mee, nooit een nieuwe majorversie.
Vanuit een pipeline
In plaats van cron kan een pipeline op Azure DevOps, GitHub Actions of GitLab CI de server bijwerken,
met de versie die je kiest. Hij draait vanuit een repository die de kit als archestack/
bevat: hij kopieert de kit via SSH naar de server en draait daar update.sh. Wat er
draait, is dan de kopie uit de repository, dus een wijziging in een Compose-bestand wordt nagekeken
en uitgerold zoals elke andere wijziging. .env verlaat de server nooit. Elke omgeving,
van ontwikkeling tot productie, is een eigen server.
Op elke server, één keer: een gebruiker waarmee de pipeline zich aanmeldt en die eigenaar is van de map van de 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)"
Pak de kit uit in /opt/archestack en doorloop stap 1 tot 3 als die gebruiker
(sudo -iu archestack). Maak daarna in het CI-systeem één omgeving per server aan,
eerst archestack-dev, met daarin:
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 Alle drie de pipelines draaien 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" - De server wordt gecontroleerd voordat er iets wordt verstuurd. Een server waarvan
de hostsleutel niet die in
SSH_KNOWN_HOSTSis, wordt geweigerd, nooit op het eerste gezicht vertrouwd. De regel hierboven wordt gemaakt uit het eigen sleutelbestand van de server, dus er zit geen netwerk tussen jou en de server. - De docker-groep staat gelijk aan root op die server, en de privésleutel ook. Maak één sleutel per server, en bewaar hem alleen in de secrets van het CI-systeem.
- Verwijder de cron-regel zodra een pipeline de server bijwerkt, anders zet de volgende nacht hem weer op de nieuwste release. Een schedule in het CI-systeem kan zijn plaats innemen.
- Kies een volledige versie, zoals
1.4.23, om acceptatie en productie precies te geven wat op test draait.
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) - Instellen. Voeg
archestack/azure-pipelines.ymltoe als pipeline. Onder Pipelines, Library bevat de variabelengroeparchestack-devde drie instellingen, en de privésleutel isarchestack-dev.keyonder Secure files. De eerste run vraagt je de pipeline beide te laten gebruiken. - Goedkeuringen. Voeg onder Pipelines, Environments een Approvals-check toe aan
archestack-prd, zodat productie op een persoon wacht.
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 }} - Instellen. Verplaats het bestand naar
.github/workflows/archestack.yml: GitHub draait alleen workflows uit die map. Onder Settings, Environments zijn de drie instellingen variabelen en isSSH_PRIVATE_KEYeen secret. Draai hem onder Actions, Archestack, Run workflow. - Required reviewers op
archestack-prdlaten productie op een persoon wachten. Op een privérepository hebben omgevingen GitHub Pro, Team of Enterprise nodig.
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 - Instellen. Neem het bestand op vanuit de eigen
.gitlab-ci.ymlvan de repository, of maak er een met alleen dit:include: - local: archestack/gitlab-ci.yml - Variabelen. Beperk ze onder Settings, CI/CD, Variables elk tot de omgeving
archestack-dev, met de privésleutel alsSSH_KEY_FILEvan het type File, en bescherm ze, zodat alleen beschermde branches ze krijgen. Draai de pipeline onder Build, Pipelines, New pipeline.
Data en back-ups
Archestack maakt zelf een back-up van elke werkruimte, in het volume archestack_backups
(zie Database Backups). Dat volume staat op dezelfde schijf als
al het andere, dus kopieer back-ups ook van de server af:
# 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 Valkuilen
- Docker publiceert poorten langs de firewall van de host. Een poort in een
Compose-bestand is open, ook wanneer ufw iets anders zegt. Manier B bindt daarom aan
127.0.0.1; houd dat zo. - Een
$in een waarde van.envwordt als variabele gelezen. Zet die waarde tussen enkele aanhalingstekens. - Een nieuwe PostgreSQL-majorversie (17) is geen wijziging van de image: de
bestanden ervan verschillen. Er is een
pg_dumpalluit de oude versie en een restore in de nieuwe voor nodig. -
docker compose down -vverwijdert de volumes, en daarmee elke werkruimte.docker compose downalleen houdt ze.
Waar dit naartoe gaat
Hosten op AKS behandelt meerdere API-replica's, upgrades zonder herstart en een pipeline per omgeving. Werkruimtes en hosting legt uit hoe één instantie veel werkruimtes bedient, en Deploymentstrategie behandelt hoe je je app vanuit een repository naar die werkruimtes deployt.