feat(deploy): add production one-click deployment
This commit is contained in:
@@ -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.
|
||||
|
||||
Executable
+70
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user