feat(deploy): add production one-click deployment

This commit is contained in:
2026-09-11 14:10:39 +08:00
parent 6eee55860f
commit a06d4f4199
5 changed files with 126 additions and 12 deletions
+13 -10
View File
@@ -9,19 +9,22 @@ sha256sum -c SHA256SUMS
docker load --input images/gochat-*-images.tgz
cp .env.example .env
# Replace every CHANGE_ME value and create the external secret/mount paths.
docker compose --env-file .env -f deploy/docker/docker-compose.prod.yml config --quiet
docker compose --env-file .env -f deploy/docker/docker-compose.prod.yml --profile ops run --rm migrate
docker compose --env-file .env -f deploy/docker/docker-compose.prod.yml up -d --wait
deploy/docker/deploy.sh .env --allow-local-images
```
`--allow-local-images` is only for this SHA256-verified bundle, whose loaded
application images use source-versioned local tags. For registry promotion,
replace both application image references with immutable `@sha256:` digests and
run the same command without that flag:
```bash
deploy/docker/deploy.sh .env
```
The bundle uses source-versioned local image tags so it can start immediately
after `docker load`; `SHA256SUMS` protects the handoff. Before registry-based
production promotion, push both images, replace the two image references in
`.env` with immutable `@sha256:` references, then run:
```bash
deploy/docker/preflight.sh .env
```
after `docker load`; `SHA256SUMS` protects the handoff. Registry-based
production promotion requires immutable `@sha256:` references and the strict
preflight performed by `deploy.sh` without `--allow-local-images`.
See `docs/ops/02-production-operations.md` for TLS, backup, rollback, and drill
requirements.
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
bundle_root=$(cd "$script_dir/../.." && pwd)
env_file=
allow_local_images=false
usage() {
cat <<'EOF'
Usage: deploy/docker/deploy.sh [env-file] [--allow-local-images]
Use --allow-local-images only with a SHA256-verified production bundle whose
application images were loaded from its images/ archive.
EOF
}
while (($#)); do
case $1 in
--allow-local-images)
allow_local_images=true
shift
;;
-h | --help)
usage
exit 0
;;
-*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
*)
if [[ -n $env_file ]]; then
echo "only one env file may be specified" >&2
usage >&2
exit 2
fi
env_file=$1
shift
;;
esac
done
env_file=${env_file:-$bundle_root/.env}
if ! env_file=$(realpath -e -- "$env_file"); then
echo "environment file does not exist: $env_file" >&2
exit 1
fi
if [[ -f $bundle_root/SHA256SUMS ]]; then
(cd "$bundle_root" && sha256sum -c SHA256SUMS)
elif [[ $allow_local_images == true ]]; then
echo "--allow-local-images requires a SHA256-verified production bundle" >&2
exit 1
fi
preflight_args=()
if [[ $allow_local_images == true ]]; then
preflight_args+=(--allow-local-images)
fi
"$script_dir/preflight.sh" "${preflight_args[@]}" "$env_file"
compose=(docker compose --env-file "$env_file" -f "$script_dir/docker-compose.prod.yml")
"${compose[@]}" config --quiet
"${compose[@]}" --profile ops run --rm migrate
"${compose[@]}" up -d --wait
echo "production deployment is ready"
+40 -2
View File
@@ -4,9 +4,40 @@ export LC_ALL=C
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
compose_args=(-f "$script_dir/docker-compose.prod.yml")
if (($#)); then
env_file=$1
env_file=
allow_local_images=false
while (($#)); do
case $1 in
--allow-local-images)
allow_local_images=true
shift
;;
--)
shift
if (($# != 1)) || [[ -n $env_file ]]; then
echo "usage: $0 [--allow-local-images] [env-file]" >&2
exit 2
fi
env_file=$1
shift
;;
-*)
echo "unknown option: $1" >&2
exit 2
;;
*)
if [[ -n $env_file ]]; then
echo "usage: $0 [--allow-local-images] [env-file]" >&2
exit 2
fi
env_file=$1
shift
;;
esac
done
if [[ -n $env_file ]]; then
set -a
# shellcheck disable=SC1090
source "$env_file"
set +a
compose_args=(--env-file "$env_file" "${compose_args[@]}")
@@ -112,6 +143,13 @@ while IFS= read -r image; do
if [[ $image =~ @sha256:[0-9a-fA-F]{64}$ ]]; then
continue
fi
if [[ $allow_local_images == true ]]; then
if ! docker image inspect "$image" >/dev/null 2>&1; then
echo "local production image is not loaded: $image" >&2
exit 1
fi
continue
fi
echo "production image must be pinned to a sha256 digest: $image" >&2
exit 1
done <<< "$images"
+2
View File
@@ -101,6 +101,8 @@ printf '%s\n' 'https://alerts.example.test/gochat' >"$ALERTMANAGER_WEBHOOK_URL_F
TEST_MUTABLE_IMAGE=1
export TEST_MUTABLE_IMAGE
expect_failure 'a mutable production image' 'must be pinned to a sha256 digest'
"$root/deploy/docker/preflight.sh" --allow-local-images > "$tmp/output" 2>&1
grep -F 'production preflight passed' "$tmp/output" >/dev/null
unset TEST_MUTABLE_IMAGE
export GOCHAT_DATABASE_DSN='postgres://external_user:external_password@db.example.test:5432/gochat?sslmode=disable'
+1
View File
@@ -80,6 +80,7 @@ files=(
deploy/docker/database_client_entrypoint.sh
deploy/docker/database_host_rejection_cases.txt
deploy/docker/preflight.sh
deploy/docker/deploy.sh
deploy/fluentd/fluent.conf
deploy/prometheus/alertmanager.yml
deploy/prometheus/blackbox.yml