Alojar o Archestack com o Docker Compose
O Archestack corre num só servidor com o Docker Compose: o PostgreSQL, a API, o builder e a aplicação publicada, com o Caddy à frente a obter cada certificado por si só. Um script leva a release mais recente do Archestack e, corrido a partir do cron todas as noites, ou por um pipeline com a versão que escolher, mantém o servidor atualizado. É a forma mais simples de alojar o Archestack. Quando um só servidor não chega, ou uma atualização não pode custar nem sequer um reinício curto, o Alojamento no AKS corre antes várias réplicas da API.
O que corre onde
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 - Uma imagem, duas shells. O builder (
web) e a aplicação publicada (web-app) são a mesma imagem web, arrancada num modo diferente. - O Caddy pergunta à API antes de cada novo certificado, por isso o endereço de um espaço de trabalho ou o domínio próprio de uma empresa recebe um da primeira vez que alguém o visita, e mais nada recebe.
- A API cria as suas próprias bases de dados no PostgreSQL vazio e migra-as quando arranca. Não há nenhum script a correr contra a base de dados.
- Os dados vivem em três volumes Docker:
archestack_postgres,archestack_filespara os uploads, earchestack_backupspara os backups do próprio Archestack. Um quarto,archestack_nuget, guarda os pacotes NuGet que os scripts usam, para que uma atualização não os descarregue outra vez.
Antes de começar
- Um servidor Linux com o Docker Engine e o seu plugin do Compose. Quatro núcleos e 8 GB de memória são um ponto de partida; Capacidade e dimensionamento tem os valores medidos.
- Uma zona DNS à qual possa acrescentar registos, com as portas 80 e 443 do servidor acessíveis a partir da internet (forma A), ou um reverse proxy no servidor que já serve um hostname (forma B).
- Um servidor de email através do qual o Archestack possa enviar, para convites e reposições de palavra-passe.
O kit inicial
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. Preencha o .env
O setup.sh cria o .env a partir do .env.example, com uma
palavra-passe da base de dados que ninguém precisa de conhecer, e legível apenas por si:
#!/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" Depois, preencha todos os CHANGE-ME nele:
# .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 - O
COMPOSE_FILEescolhe a forma de acesso. O Docker Compose lê-o do.env, por isso todos os comandosdocker composena pasta correm os ficheiros certos. - As palavras-passe ficam no servidor. O
.envé o único ficheiro que as contém; o.gitignoredo kit mantém-no fora de um repositório. - Não mexa no
POSTGRES_PASSWORDdepois de a stack ter arrancado: a base de dados mantém a palavra-passe com que foi criada.
2. A stack
A base de dados e a API, que as duas formas de acesso partilham:
# 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: A forma A acrescenta o builder, a aplicação publicada e o Caddy, em hostnames próprios:
# 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
}
Aponte editor e api sob a sua zona para o servidor, mais um wildcard
* para os endereços próprios dos espaços de trabalho. O Caddy obtém os certificados do
Let's Encrypt, por isso as portas 80 e 443 têm de estar acessíveis a partir da internet.
Sob um caminho de um hostname que já tem
A forma B coloca o Archestack sob um caminho de um hostname que um reverse proxy no servidor já
serve, como https://apps.acme.com/archestack/. Não precisa de nenhum registo DNS novo,
de nenhum endereço novo nem de nenhum certificado próprio, e as pessoas continuam a usar o endereço
que já conhecem. Não há Caddy: a API e o builder escutam apenas no servidor, e o seu proxy
envia-lhes os caminhos.
# 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" Acrescente os caminhos ao seu proxy, para o 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 para o 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
} - Não reescreva o caminho. O Archestack recebe o seu prefixo e escreve os seus próprios endereços com ele, por isso tem de o ver.
- Um proxy que seja ele próprio um container chega ao Archestack através de uma rede
Docker partilhada em vez das portas do servidor: ligue-o à rede da stack e use
api:8080eweb:80. - Mantenha os proxies de início de sessão longe dele. Um proxy que substitua o
cabeçalho
Authorizationfaz falhar todas as chamadas à API: a aplicação publicada, as chaves da Data API, os tokens de deploy, o VS Code e o MCP trazem todos o seu próprio. - O hostname é partilhado. Os tokens de início de sessão vivem no armazenamento do browser para esse endereço, que todas as aplicações nele podem ler, por isso um script injetado em qualquer uma delas chega às sessões do Archestack.
- Não disponível desta forma: os endereços dos espaços de trabalho e os domínios próprios das empresas, que precisam dos certificados a pedido do Caddy.
3. Arranque a stack
# 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 Abra o builder de imediato e crie o primeiro admin. Até alguém o fazer, quem lá chegar primeiro torna-se admin.
A release mais recente, automaticamente
O update.sh determina que release correr, escreve-a no .env e arranca a
stack com ela. A primeira execução arranca tudo; cada execução seguinte passa para uma release mais
recente quando existe uma:
#!/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 A partir do cron, todas as noites:
# crontab -e: every night at 03:00, the newest release
0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1 - Um número de versão, nunca
latest. A release mais recente é transformada no seu número, da mesma forma que o pipeline do AKS o faz, e todos os containers correm esse número. Odocker compose psdiz qual está a correr. - O
ARCHESTACK_TRACKmantém-se numa linha:1.4leva a versão1.4.xmais recente e nunca passa para1.5por si só. Defina-o com uma versão completa, como1.4.23, para ficar nessa. - Uma atualização é um reinício curto. Há um só container da API, por isso, enquanto o novo arranca e migra as bases de dados, a instância não responde: normalmente segundos, mais tempo quando uma release migra muitos espaços de trabalho. Corra a atualização noturna a uma hora de pouco movimento.
- Voltar atrás é
./update.sh 1.4.22, e depois definir oARCHESTACK_TRACKcom essa versão, senão a execução noturna seguinte volta a avançar. As bases de dados ficam como a versão mais nova as migrou, por isso prefira a release seguinte que corrija o problema. - O PostgreSQL e o Caddy seguem as tags das suas imagens (
postgres:16,caddy:2-alpine), por isso a atualização também traz as suas releases de patch, nunca uma nova versão major.
A partir de um pipeline
Em vez do cron, um pipeline no Azure DevOps, no GitHub Actions ou no GitLab CI pode atualizar o
servidor, com a versão que escolher. Corre a partir de um repositório que contém o kit como
archestack/: copia o kit para o servidor por SSH e corre lá o update.sh. A
cópia do repositório passa então a ser o que corre, por isso uma alteração a um ficheiro do Compose
é revista e aplicada como qualquer outra alteração. O .env nunca sai do servidor. Cada
ambiente, do desenvolvimento à produção, é um servidor próprio.
Em cada servidor, uma vez, um utilizador com o qual o pipeline inicia sessão, dono da pasta do 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)"
Descompacte o kit em /opt/archestack e dê os passos 1 a 3 como esse utilizador
(sudo -iu archestack). Depois, no sistema de CI, crie um ambiente por servidor,
archestack-dev primeiro, com:
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 Os três pipelines correm o 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" - O servidor é verificado antes de se enviar alguma coisa. Um servidor cuja chave de
host não seja a que está em
SSH_KNOWN_HOSTSé recusado, nunca aceite à primeira vista. A linha acima é feita a partir do próprio ficheiro de chave do servidor, por isso nenhuma rede se interpõe entre si e ele. - O grupo docker vale tanto como root nesse servidor, e a chave privada também. Crie uma chave por servidor, e guarde-a apenas nos segredos do sistema de CI.
- Remova a linha do cron assim que um pipeline atualizar o servidor, senão a noite seguinte volta a levá-lo para a release mais recente. Um agendamento no sistema de CI pode ocupar o seu lugar.
- Escolha uma versão completa, como
1.4.23, para dar à aceitação e à produção exatamente o que corre em teste.
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) - Configuração. Acrescente
archestack/azure-pipelines.ymlcomo pipeline. Em Pipelines, Library, o grupo de variáveisarchestack-devcontém as três definições, e a chave privada é o secure filearchestack-dev.key. A primeira execução pede-lhe que deixe o pipeline usar ambos. - Aprovações. Em Pipelines, Environments, acrescente uma verificação Approvals a
archestack-prdpara que a produção espere por uma pessoa.
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 }} - Configuração. Mova o ficheiro para
.github/workflows/archestack.yml: o GitHub só corre workflows a partir dessa pasta. Em Settings, Environments, as três definições são variáveis eSSH_PRIVATE_KEYé um segredo. Corra-o em Actions, Archestack, Run workflow. - Revisores obrigatórios em
archestack-prdfazem a produção esperar por uma pessoa. Num repositório privado, os ambientes precisam do 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 - Configuração. Inclua o ficheiro a partir do
.gitlab-ci.ymldo próprio repositório, ou crie um só com isto:include: - local: archestack/gitlab-ci.yml - Variáveis. Em Settings, CI/CD, Variables, limite cada uma ao ambiente
archestack-dev, com a chave privada comoSSH_KEY_FILEdo tipo File, e proteja-as, para que só as branches protegidas as recebam. Corra-o em Build, Pipelines, New pipeline.
Dados e backups
O Archestack faz o backup de cada espaço de trabalho por si só, para o volume
archestack_backups (ver Database Backups). Esse
volume está no mesmo disco que tudo o resto, por isso copie também os backups para fora do servidor:
# 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 Armadilhas
- O Docker publica portas contornando a firewall do host. Uma porta num ficheiro do
Compose fica aberta mesmo quando o ufw diz o contrário. A forma B liga-se a
127.0.0.1por essa razão; mantenha-a assim. - Um
$num valor do.envé lido como uma variável. Ponha esse valor entre aspas simples. - Uma nova versão major do PostgreSQL (17) não é uma mudança de imagem: os seus
ficheiros são diferentes. Requer um
pg_dumpallda antiga e um restauro na nova. - O
docker compose down -vapaga os volumes, e todos os espaços de trabalho com eles. Odocker compose downsozinho mantém-nos.
Para onde seguir a partir daqui
Alojamento no AKS cobre várias réplicas da API, atualizações sem reinício e um pipeline por ambiente. Espaços de trabalho e alojamento explica como uma instância serve muitos espaços de trabalho, e Estratégia de deployment trata de fazer deploy da sua aplicação para eles a partir de um repositório.