Alojar Archestack con Docker Compose
Archestack se ejecuta en un único servidor con Docker Compose: PostgreSQL, la API, el builder y la aplicación publicada, con Caddy delante obteniendo cada certificado por sí solo. Un script toma la release más reciente de Archestack y, ejecutado desde cron cada noche, o por un pipeline con la versión que elijas, mantiene el servidor actualizado. Es la forma más sencilla de alojar Archestack. Cuando un servidor no basta, o una actualización no puede costar ni siquiera un reinicio breve, Alojamiento en AKS ejecuta en su lugar varias réplicas de la API.
Qué se ejecuta dónde
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 - Una imagen, dos shells. El builder (
web) y la aplicación publicada (web-app) son la misma imagen web, arrancada en un modo distinto. - Caddy pregunta a la API antes de cada certificado nuevo, así que la dirección de un espacio de trabajo o el dominio propio de una empresa recibe uno la primera vez que alguien lo visita, y nada más lo recibe.
- La API crea sus propias bases de datos en el PostgreSQL vacío y las migra al arrancar. No hay ningún script que ejecutar contra la base de datos.
- Los datos viven en tres volúmenes de Docker:
archestack_postgres,archestack_filespara los archivos subidos yarchestack_backupspara las copias de seguridad propias de Archestack. Un cuarto,archestack_nuget, guarda los paquetes NuGet que usan los scripts, para que una actualización no los vuelva a descargar.
Antes de empezar
- Un servidor Linux con Docker Engine y su plugin de Compose. Cuatro núcleos y 8 GB de memoria son un punto de partida; Capacidad y dimensionamiento recoge las cifras medidas.
- Una zona DNS a la que puedas añadir registros, con los puertos 80 y 443 del servidor accesibles desde internet (vía A), o un proxy inverso en el servidor que ya sirve un nombre de host (vía B).
- Un servidor de correo a través del que Archestack pueda enviar, para las invitaciones y los restablecimientos de contraseña.
El kit de inicio
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. Rellena .env
setup.sh crea .env a partir de .env.example, con una contraseña
de base de datos que nadie tiene que conocer, legible solo por ti:
#!/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" Después, rellena todos los CHANGE-ME que 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_FILEelige la vía de acceso. Docker Compose lo lee de.env, así que cada comandodocker composede la carpeta ejecuta los archivos correctos. - Las contraseñas se quedan en el servidor.
.enves el único archivo que las contiene; el.gitignoredel kit lo mantiene fuera de un repositorio. - No toques
POSTGRES_PASSWORDuna vez que la pila haya arrancado: la base de datos conserva la contraseña con la que se creó.
2. La pila
La base de datos y la API, que comparten ambas vías de acceso:
# 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 vía A añade el builder, la aplicación publicada y Caddy, en nombres de host propios:
# 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
}
Apunta editor y api bajo tu zona al servidor, además de un comodín
* para las direcciones propias de los espacios de trabajo. Caddy obtiene sus
certificados de Let's Encrypt, así que los puertos 80 y 443 tienen que ser accesibles desde
internet.
Bajo una ruta de un nombre de host que ya tienes
La vía B pone Archestack bajo una ruta de un nombre de host que ya sirve un proxy inverso del
servidor, como https://apps.acme.com/archestack/. No necesita ningún registro DNS
nuevo, ninguna dirección nueva ni un certificado propio, y la gente sigue usando la dirección que ya
conoce. No hay Caddy: la API y el builder solo escuchan en el servidor, y tu proxy les envía las
rutas.
# 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" Añade las rutas a tu proxy, para 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;
} o para 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
} - No reescribas la ruta. A Archestack se le indica su prefijo y escribe sus propias direcciones con él, así que tiene que verlo.
- Un proxy que es él mismo un contenedor llega a Archestack por una red de Docker
compartida en lugar de por los puertos del servidor: únelo a la red de la pila y usa
api:8080yweb:80. - Mantén los proxies de inicio de sesión fuera de esta ruta. Uno que sustituya la
cabecera
Authorizationrompe todas las llamadas a la API: la aplicación publicada, las claves de la Data API, los tokens de despliegue, VS Code y MCP llevan todos la suya propia. - El nombre de host es compartido. Los tokens de inicio de sesión viven en el almacenamiento del navegador para esa dirección, que puede leer cualquier aplicación que esté en ella, así que un script inyectado en cualquiera de ellas llega a las sesiones de Archestack.
- No disponible por esta vía: las direcciones de los espacios de trabajo y los dominios propios de las empresas, que necesitan los certificados bajo demanda de Caddy.
3. Ponlo en marcha
# 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 Abre el builder de inmediato y crea el primer administrador. Hasta que alguien lo haga, quien llegue primero a él se convierte en el administrador.
La release más reciente, de forma automática
update.sh determina qué release ejecutar, la escribe en .env y arranca la
pila con ella. La primera ejecución lo arranca todo; cada ejecución posterior pasa a una release más
nueva cuando la hay:
#!/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 Desde cron, cada noche:
# crontab -e: every night at 03:00, the newest release
0 3 * * * /opt/archestack/update.sh >> /opt/archestack/update.log 2>&1 - Un número de versión, nunca
latest. La release más reciente se convierte en su número, de la misma forma que lo hace el pipeline de AKS, y cada contenedor ejecuta ese número.docker compose psindica cuál se ejecuta. -
ARCHESTACK_TRACKse ciñe a una línea:1.4toma la1.4.xmás reciente y nunca pasa a1.5por sí solo. Ponlo en una versión completa, como1.4.23, para quedarte en esa. - Una actualización es un reinicio breve. Hay un único contenedor de la API, así que mientras el nuevo arranca y migra las bases de datos, la instancia no responde: normalmente unos segundos, más cuando una release migra muchos espacios de trabajo. Ejecuta la actualización nocturna a una hora tranquila.
- Volver atrás es
./update.sh 1.4.22, y después ponerARCHESTACK_TRACKen esa versión, o la siguiente ejecución nocturna vuelve a avanzar. Las bases de datos se quedan como las migró la versión más nueva, así que es preferible la siguiente release que corrija el problema. - PostgreSQL y Caddy siguen sus etiquetas de imagen (
postgres:16,caddy:2-alpine), así que la actualización también trae sus releases de parche, nunca una nueva versión mayor.
Desde un pipeline
En lugar de cron, un pipeline en Azure DevOps, GitHub Actions o GitLab CI puede actualizar el
servidor, con la versión que elijas. Se ejecuta desde un repositorio que contiene el kit como
archestack/: copia el kit al servidor por SSH y ejecuta allí update.sh. Lo
que se ejecuta es entonces la copia del repositorio, así que un cambio en un archivo de Compose se
revisa y se despliega como cualquier otro cambio. .env nunca sale del servidor. Cada
entorno, de desarrollo a producción, es un servidor propio.
En cada servidor, una sola vez, un usuario con el que inicia sesión el pipeline, propietario de la carpeta 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)"
Descomprime el kit en /opt/archestack y sigue los pasos 1 a 3 como ese usuario
(sudo -iu archestack). Después, en el sistema de CI, crea un entorno por servidor,
primero archestack-dev, con:
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 Los tres pipelines ejecutan 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" - El servidor se comprueba antes de enviar nada. Uno cuya clave de host no sea la de
SSH_KNOWN_HOSTSse rechaza, nunca se da por fiable a primera vista. La línea de arriba se obtiene del propio archivo de clave del servidor, así que no hay ninguna red entre tú y él. - El grupo docker equivale a root en ese servidor, y la clave privada también. Crea una clave por servidor y guárdala solo en los secretos del sistema de CI.
- Quita la línea de cron en cuanto un pipeline actualice el servidor, o la noche siguiente lo vuelve a llevar a la release más reciente. Una programación en el sistema de CI puede ocupar su lugar.
- Elige una versión completa, como
1.4.23, para dar a aceptación y producción exactamente lo que ejecuta pruebas.
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) - Configurarlo. Añade
archestack/azure-pipelines.ymlcomo pipeline. En Pipelines, Library, el grupo de variablesarchestack-devcontiene los tres ajustes, y la clave privada es el archivo seguroarchestack-dev.key. La primera ejecución te pide que permitas al pipeline usar ambos. - Aprobaciones. En Pipelines, Environments, añade una comprobación Approvals a
archestack-prdpara que producción espere a 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 }} - Configurarlo. Mueve el archivo a
.github/workflows/archestack.yml: GitHub solo ejecuta flujos de trabajo desde esa carpeta. En Settings, Environments, los tres ajustes son variables ySSH_PRIVATE_KEYes un secreto. Ejecútalo en Actions, Archestack, Run workflow. - Los revisores obligatorios en
archestack-prdhacen que producción espere a una persona. En un repositorio privado, los entornos requieren 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 - Configurarlo. Incluye el archivo desde el
.gitlab-ci.ymlpropio del repositorio, o crea uno que contenga solo esto:include: - local: archestack/gitlab-ci.yml - Variables. En Settings, CI/CD, Variables, limita el ámbito de cada una a
archestack-dev, con la clave privada comoSSH_KEY_FILEde tipo File, y protégelas, para que solo las ramas protegidas las reciban. Ejecútalo en Build, Pipelines, New pipeline.
Datos y copias de seguridad
Archestack hace por sí solo una copia de seguridad de cada espacio de trabajo, en el volumen
archestack_backups (consulta Database Backups). Ese
volumen está en el mismo disco que todo lo demás, así que haz también una copia de las copias de
seguridad fuera del 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 A tener en cuenta
- Docker publica los puertos saltándose el cortafuegos del host. Un puerto en un
archivo de Compose está abierto aunque ufw diga lo contrario. Por eso la vía B se enlaza a
127.0.0.1; déjalo así. - Un
$en un valor de.envse lee como una variable. Pon ese valor entre comillas simples. - Una nueva versión mayor de PostgreSQL (17) no es un cambio de imagen: sus archivos
son distintos. Requiere un
pg_dumpallde la antigua y una restauración en la nueva. -
docker compose down -vborra los volúmenes, y con ellos todos los espacios de trabajo.docker compose downa secas los conserva.
Hacia dónde va esto a continuación
Alojamiento en AKS cubre varias réplicas de la API, actualizaciones sin reinicio y un pipeline por entorno. Espacios de trabajo y alojamiento explica cómo una instancia atiende a muchos espacios de trabajo, y Estrategia de despliegue describe cómo desplegar tu aplicación en ellos desde un repositorio.