Add production Docker Compose file for external PostgreSQL integration
- Introduced `docker-compose.prod.yml` for production deployment. - Configured service to connect to an external PostgreSQL instance. - Set environment variables for JWT and database connection strings. - Defined network and volume for data protection keys.
This commit is contained in:
106
deploy.sh
106
deploy.sh
@@ -1,15 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy.sh — Build and deploy OTSSignsOrchestrator to a Docker Swarm manager over SSH
|
||||
# deploy.sh — Sync source and build on a Docker Swarm manager over SSH
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration ─────────────────────────────────────────────────────────────
|
||||
SWARM_HOST="${SWARM_HOST:-}" # e.g. user@10.0.0.1 or set via env
|
||||
IMAGE_NAME="${IMAGE_NAME:-otssigns-orchestrator}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-ots-orchestrator}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
||||
STACK_NAME="${STACK_NAME:-otssigns}"
|
||||
COMPOSE_FILE="docker-compose.yml"
|
||||
REMOTE_DIR="${REMOTE_DIR:-/opt/otssigns}"
|
||||
STACK_NAME="${STACK_NAME:-otsorchestrator-dev}"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" # use docker-compose.prod.yml for prod
|
||||
REMOTE_DIR="${REMOTE_DIR:-/stack/dev/orchestrator}"
|
||||
ENV_FILE="${ENV_FILE:-.env}" # local .env file with secrets
|
||||
FRESH_DEPLOY="${FRESH_DEPLOY:-0}" # set to 1 to tear down old stack+volumes before deploying
|
||||
|
||||
# ── Validate ──────────────────────────────────────────────────────────────────
|
||||
if [[ -z "$SWARM_HOST" ]]; then
|
||||
@@ -25,24 +26,68 @@ if [[ ! -f "$COMPOSE_FILE" ]]; then
|
||||
fi
|
||||
|
||||
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
BUILD_FLAGS=""
|
||||
[[ "$FRESH_DEPLOY" == "1" ]] && BUILD_FLAGS="--no-cache"
|
||||
|
||||
echo "==> Deploying ${FULL_IMAGE} to swarm manager: ${SWARM_HOST}"
|
||||
echo " Stack: ${STACK_NAME}"
|
||||
echo " Remote dir: ${REMOTE_DIR}"
|
||||
echo " Stack: ${STACK_NAME}"
|
||||
echo " Compose: ${COMPOSE_FILE}"
|
||||
echo " Remote dir: ${REMOTE_DIR}"
|
||||
echo " Fresh: ${FRESH_DEPLOY}"
|
||||
echo ""
|
||||
|
||||
# ── Step 1: Build the image locally ──────────────────────────────────────────
|
||||
echo "==> [1/5] Building Docker image..."
|
||||
docker build -t "${FULL_IMAGE}" .
|
||||
# ── Step 0 (fresh only): Tear down existing stack and volumes ─────────────────
|
||||
if [[ "$FRESH_DEPLOY" == "1" ]]; then
|
||||
echo "==> [0/4] Tearing down existing stack '${STACK_NAME}'..."
|
||||
ssh "${SWARM_HOST}" "
|
||||
if docker stack ls --format '{{.Name}}' | grep -q '^${STACK_NAME}$'; then
|
||||
docker stack rm ${STACK_NAME}
|
||||
echo ' Waiting for services to stop...'
|
||||
timeout=60
|
||||
while docker service ls --filter name=${STACK_NAME} --format '{{.Name}}' 2>/dev/null | grep -q .; do
|
||||
sleep 2
|
||||
timeout=\$(( timeout - 2 ))
|
||||
[[ \$timeout -le 0 ]] && break
|
||||
done
|
||||
# Wait for containers/tasks to fully exit so volumes are released
|
||||
echo ' Waiting for containers to exit...'
|
||||
timeout=30
|
||||
while docker ps -q --filter name=${STACK_NAME} 2>/dev/null | grep -q .; do
|
||||
sleep 2
|
||||
timeout=\$(( timeout - 2 ))
|
||||
[[ \$timeout -le 0 ]] && break
|
||||
done
|
||||
else
|
||||
echo ' No existing stack found, skipping removal.'
|
||||
fi
|
||||
# Remove volumes created by the stack (stack-name prefix) or by docker compose
|
||||
# (compose project-name prefix, which defaults to the remote directory basename).
|
||||
echo ' Removing stack volumes...'
|
||||
for vol in \$(docker volume ls --format '{{.Name}}' | grep -E '^(${STACK_NAME}_|orchestrator_)'); do
|
||||
# Force-remove any containers still holding this volume before deleting it
|
||||
containers=\$(docker ps -aq --filter volume=\"\$vol\" 2>/dev/null)
|
||||
if [[ -n \"\$containers\" ]]; then
|
||||
echo \" Force-removing containers holding volume \$vol...\"
|
||||
echo \"\$containers\" | xargs docker rm -f 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
docker volume rm \"\$vol\" && echo \" Removed volume: \$vol\" || echo \" WARNING: Could not remove volume: \$vol (may still be in use)\"
|
||||
done
|
||||
"
|
||||
echo " Stack removed."
|
||||
fi
|
||||
|
||||
# ── Step 2: Save and stream the image to the remote host ─────────────────────
|
||||
echo "==> [2/5] Transferring image to ${SWARM_HOST}..."
|
||||
docker save "${FULL_IMAGE}" | ssh "${SWARM_HOST}" "docker load"
|
||||
|
||||
# ── Step 3: Prepare the remote directory and upload compose file + env ────────
|
||||
echo "==> [3/5] Uploading compose file and environment..."
|
||||
# ── Step 1: Sync source to the remote host ───────────────────────────────────
|
||||
echo "==> [1/4] Syncing source to ${SWARM_HOST}:${REMOTE_DIR}..."
|
||||
ssh "${SWARM_HOST}" "mkdir -p ${REMOTE_DIR}"
|
||||
scp "${COMPOSE_FILE}" "${SWARM_HOST}:${REMOTE_DIR}/docker-compose.yml"
|
||||
rsync -az --delete \
|
||||
--exclude 'bin/' \
|
||||
--exclude 'obj/' \
|
||||
--exclude 'node_modules/' \
|
||||
--exclude '.git/' \
|
||||
--exclude 'logs/' \
|
||||
--exclude 'wwwroot/' \
|
||||
./ "${SWARM_HOST}:${REMOTE_DIR}/"
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
scp "${ENV_FILE}" "${SWARM_HOST}:${REMOTE_DIR}/.env"
|
||||
@@ -51,27 +96,26 @@ else
|
||||
echo " WARNING: No .env file found at '${ENV_FILE}'. Secrets must already exist on the remote host."
|
||||
fi
|
||||
|
||||
# ── Step 4: Apply EF Core migrations before deploying the new stack ───────────
|
||||
# The app runs migrations on startup (UseMigrationsEndPoint / MigrateAsync in Program.cs).
|
||||
# If you need explicit pre-deploy migrations, uncomment the block below and
|
||||
# ensure the remote host can reach PostgreSQL directly.
|
||||
#
|
||||
# echo "==> [3b] Running EF Core migrations..."
|
||||
# ssh "${SWARM_HOST}" "docker run --rm --env-file ${REMOTE_DIR}/.env \
|
||||
# -e ConnectionStrings__OrchestratorDb='...' \
|
||||
# ${FULL_IMAGE} dotnet OTSSignsOrchestrator.dll --migrate-only"
|
||||
# ── Step 2: Build the image on the remote host ───────────────────────────────
|
||||
echo "==> [2/4] Building Docker image on ${SWARM_HOST}..."
|
||||
ssh "${SWARM_HOST}" "cd ${REMOTE_DIR} && docker build ${BUILD_FLAGS} -t ${FULL_IMAGE} ."
|
||||
|
||||
# ── Step 5: Deploy the stack ──────────────────────────────────────────────────
|
||||
echo "==> [4/5] Deploying stack '${STACK_NAME}'..."
|
||||
# ── Step 3: Deploy the stack ──────────────────────────────────────────────────
|
||||
echo "==> [3/4] Deploying stack '${STACK_NAME}'..."
|
||||
# docker stack deploy does NOT load .env automatically — use `docker compose config`
|
||||
# to resolve all variable substitutions first, then pipe the rendered YAML to the deploy.
|
||||
# -p ensures compose uses the stack name as the project name so volume names are consistent.
|
||||
ssh "${SWARM_HOST}" "cd ${REMOTE_DIR} && \
|
||||
docker compose -p ${STACK_NAME} --env-file .env -f ${COMPOSE_FILE} config | \
|
||||
sed '/^name:/d; s/published: \"\([0-9]*\)\"/published: \1/g' | \
|
||||
docker stack deploy \
|
||||
--compose-file docker-compose.yml \
|
||||
--compose-file - \
|
||||
--with-registry-auth \
|
||||
--prune \
|
||||
${STACK_NAME}"
|
||||
|
||||
# ── Step 6: Verify rollout ────────────────────────────────────────────────────
|
||||
echo "==> [5/5] Waiting for service to converge..."
|
||||
# ── Step 4: Verify rollout ───────────────────────────────────────────────────
|
||||
echo "==> [4/4] Waiting for service to converge..."
|
||||
ssh "${SWARM_HOST}" "docker service ls --filter name=${STACK_NAME}"
|
||||
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user