fix prod deployment
Build & Deploy / Build & Push Docker Image (push) Failing after 34s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 57s
Test / API Unit Tests (push) Failing after 52s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Storefront Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 41s
Test / Dashboard Unit Tests (push) Successful in 43s
Test / API Integration Tests (push) Has been cancelled

This commit is contained in:
root
2026-07-01 01:37:37 -04:00
parent 13d0512048
commit 5dfd5b1814
37 changed files with 2156 additions and 61 deletions
+110 -12
View File
@@ -52,6 +52,37 @@ read_env_value() {
printf '%s' "${value}"
}
export_env_value_if_unset() {
local key="$1"
local value
if [[ -n "${!key:-}" ]]; then
return 0
fi
value="$(read_env_value "${key}")"
if [[ -n "${value}" ]]; then
export "${key}=${value}"
fi
}
load_prod_env_exports() {
local key
for key in \
APP_IMAGE \
APP_VERSION \
IMAGE_TAG \
IMAGE_ARCHIVE_DIR \
IMAGE_ARCHIVE_REQUIRED \
REGISTRY_HOST \
REGISTRY_USER \
REGISTRY_PASSWORD
do
export_env_value_if_unset "${key}"
done
}
validate_prod_env_file() {
local api_domain public_site_domain cors_origins portainer_domain postgres_password redis_password jwt_secret
@@ -113,7 +144,7 @@ ensure_dynamic_dir() {
}
prod_compose() {
docker compose -p "${PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${PROD_COMPOSE_FILE}" "$@"
APP_ENV_FILE="${ENV_FILE}" DOCKER_PROD_API_UPLOADS_VOLUME="${API_UPLOADS_VOLUME}" docker compose -p "${PROJECT_NAME}" --env-file "${ENV_FILE}" -f "${PROD_COMPOSE_FILE}" "$@"
}
portainer_compose() {
@@ -154,25 +185,45 @@ start_prod_services() {
}
require_release_image() {
if [[ -z "${APP_IMAGE:-}" || -z "${APP_VERSION:-}" ]]; then
echo "APP_IMAGE and APP_VERSION must be set for pull-based production deploys." >&2
IMAGE_TAG="${IMAGE_TAG:-${APP_VERSION:-}}"
export IMAGE_TAG
if [[ -z "${IMAGE_TAG:-}" ]]; then
echo "IMAGE_TAG must be set for production deploys." >&2
exit 1
fi
}
login_registry_if_configured() {
local configured=0
[[ -n "${REGISTRY_HOST:-}" ]] && configured=$((configured + 1))
[[ -n "${REGISTRY_USER:-}" ]] && configured=$((configured + 1))
[[ -n "${REGISTRY_PASSWORD:-}" ]] && configured=$((configured + 1))
if [[ ${configured} -eq 0 ]]; then
if [[ "${IMAGE_ARCHIVE_REQUIRED:-false}" == "true" || "${IMAGE_ARCHIVE_REQUIRED:-0}" == "1" ]]; then
echo "IMAGE_ARCHIVE_REQUIRED is enabled; skipping docker login."
return 0
fi
if [[ ${configured} -ne 3 ]]; then
echo "REGISTRY_HOST, REGISTRY_USER, and REGISTRY_PASSWORD must either all be set or all be unset." >&2
if [[ -n "${IMAGE_ARCHIVE_DIR:-}" && -d "${IMAGE_ARCHIVE_DIR}" ]]; then
shopt -s nullglob
local archives=(
"${IMAGE_ARCHIVE_DIR}"/*"${IMAGE_TAG:-latest}"*.tar
"${IMAGE_ARCHIVE_DIR}"/*"${IMAGE_TAG:-latest}"*.tar.gz
"${IMAGE_ARCHIVE_DIR}"/*"${IMAGE_TAG:-latest}"*.tgz
)
shopt -u nullglob
if [[ ${#archives[@]} -gt 0 && -z "${REGISTRY_USER:-}" && -z "${REGISTRY_PASSWORD:-}" ]]; then
echo "Found image archive in IMAGE_ARCHIVE_DIR; skipping registry login."
return 0
fi
fi
if [[ -z "${REGISTRY_USER:-}" && -z "${REGISTRY_PASSWORD:-}" ]]; then
if [[ -n "${REGISTRY_HOST:-}" ]]; then
echo "REGISTRY_HOST is set but no registry credentials are configured; skipping docker login."
fi
return 0
fi
if [[ -z "${REGISTRY_HOST:-}" || -z "${REGISTRY_USER:-}" || -z "${REGISTRY_PASSWORD:-}" ]]; then
echo "REGISTRY_HOST, REGISTRY_USER, and REGISTRY_PASSWORD are all required when registry credentials are configured." >&2
exit 1
fi
@@ -181,9 +232,56 @@ login_registry_if_configured() {
pull_prod_release_images() {
require_release_image
if load_release_image_archives; then
return 0
fi
if [[ "${IMAGE_ARCHIVE_REQUIRED:-false}" == "true" || "${IMAGE_ARCHIVE_REQUIRED:-0}" == "1" ]]; then
echo "IMAGE_ARCHIVE_REQUIRED is enabled, but no archive for IMAGE_TAG=${IMAGE_TAG} was found in ${IMAGE_ARCHIVE_DIR:-<unset>}." >&2
echo "The VPS cannot reach the VPN-only registry. Create a shared archive first, for example:" >&2
echo " docker save ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG} -o ${IMAGE_ARCHIVE_DIR:-./image-archives}/carmanagement-${IMAGE_TAG}.tar" >&2
exit 1
fi
prod_compose pull migrate api storefront dashboard admin
}
load_release_image_archives() {
local archive_dir="${IMAGE_ARCHIVE_DIR:-}"
local archive
local loaded=0
if [[ -z "${archive_dir}" ]]; then
return 1
fi
if [[ ! -d "${archive_dir}" ]]; then
echo "IMAGE_ARCHIVE_DIR is set but does not exist: ${archive_dir}" >&2
exit 1
fi
shopt -s nullglob
for archive in \
"${archive_dir}"/*"${IMAGE_TAG}"*.tar \
"${archive_dir}"/*"${IMAGE_TAG}"*.tar.gz \
"${archive_dir}"/*"${IMAGE_TAG}"*.tgz
do
echo "Loading Docker image archive: ${archive}"
docker load -i "${archive}"
loaded=1
done
shopt -u nullglob
if [[ ${loaded} -eq 0 ]]; then
echo "No Docker image archive found for IMAGE_TAG=${IMAGE_TAG} in ${archive_dir}."
return 1
fi
echo "Loaded image archive(s) for ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG}; skipping docker pull."
return 0
}
run_prod_migrations() {
prod_compose run --rm migrate
}
+3 -2
View File
@@ -5,9 +5,10 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "${ROOT_DIR}/scripts/docker-prod-common.sh"
echo "Deploying production release ${APP_IMAGE:-<missing>}:${APP_VERSION:-<missing>}"
echo "Deploying production release ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG:-${APP_VERSION:-<missing>}}"
ensure_env_file
load_prod_env_exports
ensure_traefik_network
ensure_api_uploads_volume
require_release_image
@@ -34,4 +35,4 @@ wait_for_healthy admin 180
echo "Pruning old images"
docker image prune -f >/dev/null
echo "Production release ${APP_IMAGE}:${APP_VERSION} is up and healthy."
echo "Production release ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG} is up and healthy."