#!/usr/bin/env bash
# Absolute DB v9.5.1
# Copyright (c) 2024-2026 D.H.Maree. All rights reserved.
# Author/Creator: David H Maree
# Owner: D.H.Maree (ABN 21 498 105 915) — sole IP holder
# Licensed to: SupportCALL AU — primary distributor
# SPDX-License-Identifier: BSL-1.1
# https://absolutedb.com/
# https://downloads.absolutedb.com/install-docker.sh
#
# Deploys Absolute DB as a Docker container with optional docker-compose setup.
# Requires Docker Engine 20.10+ or Docker Desktop.
#
# Usage:
# bash install-docker.sh
# bash install-docker.sh --no-service # create compose file but don't start
# bash install-docker.sh --no-test # skip connection verification
# bash install-docker.sh --uninstall # remove container and optionally volumes
# bash install-docker.sh --prefix=/myapp # directory for docker-compose.yml
# bash install-docker.sh --branch=9.0.2 # specific image tag
#
# Copyright 2024-2026 SupportCALL AU & D.H.Maree
# SPDX-License-Identifier: BSL-1.1
set -euo pipefail
# ── Constants ─────────────────────────────────────────────────────────────────
VERSION="9.5.1"
REPO="https://github.com/supportcall/AbsoluteDB.git"
IMAGE_NAME="absolutedb/absdb"
IMAGE_TAG="${VERSION}"
CONTAINER_NAME="absdb"
VOLUME_NAME="absdb_data"
PG_PORT=5433
REST_PORT=8080
GRPC_PORT=9090
COMPOSE_DIR="${PWD}"
LOG_FILE="/tmp/absdb-docker-install-$$.log"
MIN_DOCKER_MAJOR=20
# ── Colour helpers ────────────────────────────────────────────────────────────
GRN='\033[0;32m'; BLU='\033[0;34m'; YLW='\033[0;33m'
RED='\033[0;31m'; CYN='\033[0;36m'; NC='\033[0m'; BOLD='\033[1m'
info() { echo -e "${BLU}[INFO]${NC} $*"; }
ok() { echo -e "${GRN}[ OK ]${NC} $*"; }
warn() { echo -e "${YLW}[WARN]${NC} $*"; }
step() { echo -e "\n${BOLD}${CYN}$*${NC}"; }
die() {
echo -e "\n${RED}${BOLD}[FAIL]${NC} $*" >&2
echo -e "${YLW}${BOLD}Troubleshooting guide:${NC}" >&2
echo -e " 1. Full install log: ${LOG_FILE}" >&2
echo -e " 2. Re-run with verbose output: bash -x install-docker.sh" >&2
echo -e " 3. Docker daemon not running?" >&2
echo -e " Linux: sudo systemctl start docker && sudo systemctl enable docker" >&2
echo -e " macOS: Open Docker Desktop application" >&2
echo -e " Windows: Start Docker Desktop from system tray" >&2
echo -e " 4. Permission denied on Docker socket?" >&2
echo -e " sudo usermod -aG docker \$USER then log out and back in" >&2
echo -e " 5. Port conflicts?" >&2
echo -e " Check: ss -tlnp | grep -E '${PG_PORT}|${REST_PORT}|${GRPC_PORT}'" >&2
echo -e " Or: netstat -tlnp | grep -E '${PG_PORT}|${REST_PORT}|${GRPC_PORT}'" >&2
echo -e " 6. Image pull failed?" >&2
echo -e " Check network/firewall. Try: docker pull ${IMAGE_NAME}:${IMAGE_TAG}" >&2
echo -e " 7. Volume permission issues?" >&2
echo -e " docker volume rm ${VOLUME_NAME} && re-run" >&2
echo -e " 8. Low disk space for image?" >&2
echo -e " Check: docker system df" >&2
echo -e " Clean: docker system prune" >&2
echo -e " 9. Container exits immediately?" >&2
echo -e " Check: docker logs ${CONTAINER_NAME}" >&2
echo -e " 10. See full docs: https://absolutedb.com/docs/docker" >&2
exit 1
}
# Redirect all output to log file while keeping terminal output
exec > >(tee -a "${LOG_FILE}") 2>&1
# ── Argument parsing ──────────────────────────────────────────────────────────
OPT_NO_SERVICE=0
OPT_NO_TEST=0
OPT_UNINSTALL=0
for a in "$@"; do
case "$a" in
--no-service) OPT_NO_SERVICE=1 ;;
--no-test) OPT_NO_TEST=1 ;;
--uninstall) OPT_UNINSTALL=1 ;;
--prefix=*) COMPOSE_DIR="${a#--prefix=}" ;;
--branch=*) IMAGE_TAG="${a#--branch=}" ;;
--help|-h)
echo "Usage: bash install-docker.sh [OPTIONS]"
echo " --no-service Create compose file but do not start container"
echo " --no-test Skip connection verification after start"
echo " --uninstall Remove container, image, and optionally volumes"
echo " --prefix=
Directory to create docker-compose.yml (default: \$PWD)"
echo " --branch= Docker image tag to use (default: ${VERSION})"
exit 0
;;
*) warn "Unknown option: $a (ignored)" ;;
esac
done
# ── Banner ────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}${GRN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GRN}║ Absolute DB v${VERSION} — Docker Installer ║${NC}"
echo -e "${BOLD}${GRN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " Image : ${BOLD}${IMAGE_NAME}:${IMAGE_TAG}${NC}"
echo -e " Container : ${BOLD}${CONTAINER_NAME}${NC}"
echo -e " Volume : ${BOLD}${VOLUME_NAME}${NC}"
echo -e " Compose : ${BOLD}${COMPOSE_DIR}/docker-compose.yml${NC}"
echo -e " Ports : ${BOLD}${PG_PORT} (PG) / ${REST_PORT} (REST) / ${GRPC_PORT} (gRPC)${NC}"
echo -e " Log : ${BOLD}${LOG_FILE}${NC}"
echo ""
# ── Step 1: Check Docker installation ─────────────────────────────────────────
step "Step 1/4: Checking Docker"
DOCKER_CMD=""
if command -v docker &>/dev/null; then
DOCKER_CMD="docker"
elif command -v podman &>/dev/null; then
# Podman is Docker-compatible (rootless, daemonless)
DOCKER_CMD="podman"
info "Podman detected — using as Docker-compatible runtime."
fi
if [ -z "${DOCKER_CMD}" ]; then
warn "Docker is not installed."
echo ""
echo -e "${BOLD} Install Docker:${NC}"
echo ""
# Detect OS for install instructions
OS_TYPE=$(uname -s)
if [ "${OS_TYPE}" = "Linux" ]; then
if [ -f /etc/debian_version ] || grep -qi debian /etc/os-release 2>/dev/null || grep -qi ubuntu /etc/os-release 2>/dev/null; then
echo -e " ${CYN}Ubuntu / Debian:${NC}"
echo " sudo apt-get update"
echo " sudo apt-get install -y docker.io docker-compose-plugin"
echo " sudo systemctl enable --now docker"
echo " sudo usermod -aG docker \$USER # log out then back in"
echo ""
fi
if [ -f /etc/redhat-release ] || grep -qi "rhel\|centos\|fedora\|rocky\|alma" /etc/os-release 2>/dev/null; then
echo -e " ${CYN}RHEL / CentOS / Fedora / Rocky:${NC}"
echo " sudo dnf install -y docker"
echo " sudo systemctl enable --now docker"
echo " sudo usermod -aG docker \$USER # log out then back in"
echo ""
fi
echo -e " ${CYN}Official Docker Engine (all Linux distros):${NC}"
echo " curl -fsSL https://get.docker.com | bash"
echo ""
elif [ "${OS_TYPE}" = "Darwin" ]; then
echo -e " ${CYN}macOS:${NC}"
echo " 1. Download Docker Desktop: https://www.docker.com/products/docker-desktop"
echo " 2. Open the .dmg and drag to Applications"
echo " 3. Launch Docker Desktop from Applications"
echo " 4. Wait for Docker to start (whale icon in menu bar)"
echo ""
echo " Alternatively via Homebrew:"
echo " brew install --cask docker"
echo ""
fi
die "Docker is required. Install it using the instructions above, then re-run this script."
fi
# Verify Docker daemon is running
_docker_err=$(docker info 2>&1 || true)
if ! docker info &>/dev/null 2>&1; then
OS_TYPE=$(uname -s)
if echo "${_docker_err}" | grep -qi "permission denied"; then
echo -e "${RED}Permission denied accessing Docker socket.${NC}" >&2
echo " Your user is not in the 'docker' group." >&2
echo " Fix: sudo usermod -aG docker \$(whoami) && newgrp docker" >&2
echo " Then re-run this installer." >&2
die "Docker permission denied. Add your user to the docker group."
else
echo -e "${RED}Docker daemon is not running.${NC}" >&2
if [ "${OS_TYPE}" = "Linux" ]; then
echo " Start it: sudo systemctl start docker" >&2
echo " Enable on boot: sudo systemctl enable docker" >&2
elif [ "${OS_TYPE}" = "Darwin" ]; then
echo " Start Docker Desktop from the Applications folder or menu bar." >&2
fi
die "Docker daemon is not running. Start Docker and try again."
fi
fi
# Check Docker version
DOCKER_VERSION=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1 || echo "0.0")
DOCKER_MAJOR=$(echo "${DOCKER_VERSION}" | cut -d. -f1)
if [ "${DOCKER_MAJOR:-0}" -lt "${MIN_DOCKER_MAJOR}" ] 2>/dev/null; then
warn "Docker ${DOCKER_VERSION} is older than the recommended version (${MIN_DOCKER_MAJOR}.x+)."
warn "Consider upgrading: https://docs.docker.com/engine/install/"
else
ok "Docker ${DOCKER_VERSION}"
fi
# Check disk space
AVAIL_KB=$(df -k "${HOME}" 2>/dev/null | awk 'NR==2 {print $4}' || echo 999999)
if [ "${AVAIL_KB:-0}" -lt 2097152 ]; then
warn "Less than 2 GB disk space available. Docker image requires ~200 MB, plus data volume."
warn "Check: docker system df && docker system prune"
else
ok "Disk space: $(( ${AVAIL_KB:-0} / 1024 )) MB available"
fi
# ── Uninstall ─────────────────────────────────────────────────────────────────
if [ "${OPT_UNINSTALL}" -eq 1 ]; then
step "Uninstalling Absolute DB Docker deployment..."
docker stop "${CONTAINER_NAME}" 2>/dev/null && ok "Container stopped" || true
docker rm "${CONTAINER_NAME}" 2>/dev/null && ok "Container removed" || true
docker rmi "${IMAGE_NAME}:${IMAGE_TAG}" 2>/dev/null && ok "Image removed" || true
rm -f "${COMPOSE_DIR}/docker-compose.yml"
echo ""
read -r -p " Remove persistent data volume '${VOLUME_NAME}'? [y/N] " CONFIRM || CONFIRM="n"
if [ "${CONFIRM}" = "y" ] || [ "${CONFIRM}" = "Y" ]; then
docker volume rm "${VOLUME_NAME}" 2>/dev/null && ok "Volume ${VOLUME_NAME} removed" || warn "Volume not found"
else
warn "Volume '${VOLUME_NAME}' retained. Remove manually: docker volume rm ${VOLUME_NAME}"
fi
ok "Absolute DB Docker deployment removed."
exit 0
fi
# ── Step 2: Pull or build image ────────────────────────────────────────────────
step "Step 2/4: Getting Absolute DB Docker image"
# Check if image already exists locally
if docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" &>/dev/null 2>&1; then
ok "Image ${IMAGE_NAME}:${IMAGE_TAG} already present locally"
info "To force a fresh pull: docker rmi ${IMAGE_NAME}:${IMAGE_TAG}"
else
info "Pulling ${IMAGE_NAME}:${IMAGE_TAG} from Docker Hub..."
if docker pull "${IMAGE_NAME}:${IMAGE_TAG}" 2>/dev/null; then
ok "Image pulled: ${IMAGE_NAME}:${IMAGE_TAG}"
else
warn "Pull from Docker Hub failed — attempting to build from source..."
info "Cloning source repository for local build..."
BUILD_DIR=$(mktemp -d)
_attempt=1; _delay=4
while [ "${_attempt}" -le 3 ]; do
git clone --depth=1 "${REPO}" "${BUILD_DIR}/AbsoluteDB" 2>&1 && break
if [ "${_attempt}" -lt 3 ]; then
warn "Clone attempt ${_attempt}/3 failed — retrying in ${_delay}s..."
sleep "${_delay}"; _delay=$(( _delay * 2 ))
else
die "git clone failed after 3 attempts. Install git or check network: sudo apt-get install git"
fi
_attempt=$(( _attempt + 1 ))
done
info "Building Docker image (this may take 5-15 minutes)..."
docker build \
--tag "${IMAGE_NAME}:${IMAGE_TAG}" \
--label "version=${VERSION}" \
--label "build-date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--file "${BUILD_DIR}/AbsoluteDB/deploy/docker/Dockerfile" \
"${BUILD_DIR}/AbsoluteDB" || die "docker build failed. Check Dockerfile in repository."
rm -rf "${BUILD_DIR}"
ok "Image built locally: ${IMAGE_NAME}:${IMAGE_TAG}"
fi
fi
# Verify image
IMAGE_SIZE=$(docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format='{{.Size}}' 2>/dev/null || echo 0)
IMAGE_SIZE_MB=$(( IMAGE_SIZE / 1048576 ))
ok "Image size: ${IMAGE_SIZE_MB} MB"
# ── Step 3: Create docker-compose.yml ─────────────────────────────────────────
step "Step 3/4: Creating deployment configuration"
mkdir -p "${COMPOSE_DIR}"
COMPOSE_FILE="${COMPOSE_DIR}/docker-compose.yml"
if [ -f "${COMPOSE_FILE}" ]; then
cp "${COMPOSE_FILE}" "${COMPOSE_FILE}.bak.$(date +%Y%m%d-%H%M%S)"
info "Existing docker-compose.yml backed up"
fi
cat > "${COMPOSE_FILE}" </dev/null 2>&1; then
info "Stopping existing ${CONTAINER_NAME} container..."
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
fi
# Check for port conflicts before starting
PORT_CONFLICT=0
for PORT in "${PG_PORT}" "${REST_PORT}" "${GRPC_PORT}"; do
_port_in_use=false
if (ss -tlnp 2>/dev/null | grep -qE ":${PORT}[[:space:]]|:${PORT}$") 2>/dev/null || \
(netstat -tlnp 2>/dev/null | grep -qE ":${PORT}[[:space:]]|:${PORT}$") 2>/dev/null || \
(lsof -iTCP:"${PORT}" -sTCP:LISTEN &>/dev/null 2>/dev/null); then
_port_in_use=true
fi
if [ "${_port_in_use}" = true ]; then
warn "Port ${PORT} is already in use. Another process may be listening."
PORT_CONFLICT=1
fi
done
if [ "${PORT_CONFLICT}" -eq 1 ]; then
warn "Port conflict detected. The container may fail to start."
warn "Check: ss -tlnp | grep -E '${PG_PORT}|${REST_PORT}|${GRPC_PORT}'"
fi
info "Starting ${CONTAINER_NAME} container..."
docker run -d \
--name "${CONTAINER_NAME}" \
--restart unless-stopped \
-p "${PG_PORT}:5433" \
-p "${REST_PORT}:8080" \
-p "${GRPC_PORT}:9090" \
-v "${VOLUME_NAME}:/var/lib/absdb" \
-e "ABSDB_LOG_LEVEL=info" \
"${IMAGE_NAME}:${IMAGE_TAG}" || die "docker run failed. Check: docker logs ${CONTAINER_NAME}"
ok "Container started: ${CONTAINER_NAME}"
# Wait for container to be healthy
info "Waiting for Absolute DB to initialise..."
STARTED=0
for i in $(seq 1 30); do
sleep 1
CONTAINER_STATUS=$(docker inspect "${CONTAINER_NAME}" --format='{{.State.Status}}' 2>/dev/null || echo "unknown")
HEALTH_STATUS=$(docker inspect "${CONTAINER_NAME}" --format='{{.State.Health.Status}}' 2>/dev/null || echo "none")
if [ "${CONTAINER_STATUS}" = "running" ]; then
# Use Docker's built-in healthcheck status — avoids need for curl inside container
if [ "${HEALTH_STATUS}" = "healthy" ]; then
ok "Absolute DB is healthy (${i}s)"
STARTED=1
break
fi
# Fallback: try REST API from host if healthcheck not yet reporting
HEALTH=$(curl -sf --connect-timeout 2 "http://localhost:${REST_PORT}/health" 2>/dev/null || true)
if echo "${HEALTH}" | grep -q '"healthy":true\|"status"'; then
ok "Absolute DB REST API responding (${i}s)"
STARTED=1
break
fi
elif [ "${CONTAINER_STATUS}" = "exited" ]; then
docker logs "${CONTAINER_NAME}" --tail 20 >&2
die "Container exited unexpectedly. Check logs above."
fi
done
if [ "${STARTED}" -eq 0 ]; then
warn "Container did not become healthy within 30 seconds."
warn " Container status: $(docker inspect ${CONTAINER_NAME} --format='{{.State.Status}}' 2>/dev/null || echo 'unknown')"
warn " Check logs: docker logs ${CONTAINER_NAME}"
fi
# Verify from host
if [ "${OPT_NO_TEST}" -eq 0 ]; then
echo ""
info "Verifying connectivity from host..."
# REST API
REST_OK=0
HEALTH=$(curl -sf --connect-timeout 5 "http://localhost:${REST_PORT}/health" 2>/dev/null || true)
if echo "${HEALTH}" | grep -q '"status"'; then
ok "REST API: http://localhost:${REST_PORT}/health → ${HEALTH}"
REST_OK=1
else
warn "REST API not responding on port ${REST_PORT}"
fi
# PostgreSQL connection (if psql available)
if command -v psql &>/dev/null; then
PSQL_OK=$(PGPASSWORD="" psql -h localhost -p "${PG_PORT}" -U absdb \
-c "SELECT 'connected' AS status" -t 2>/dev/null | xargs || true)
if echo "${PSQL_OK}" | grep -q "connected"; then
ok "PostgreSQL wire: localhost:${PG_PORT} → connected"
else
warn "PostgreSQL connection test failed (psql may need credentials)"
fi
fi
fi
# Show container status
echo ""
info "Container status:"
docker ps --filter "name=${CONTAINER_NAME}" --format " {{.Names}}\t{{.Status}}\t{{.Ports}}"
fi
# ── Final summary ─────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}${GRN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GRN}║ Absolute DB v${VERSION} — Docker deployment ready! ║${NC}"
echo -e "${BOLD}${GRN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BOLD} Quick start:${NC}"
echo ""
echo -e " ${CYN}REST API:${NC}"
echo " curl http://localhost:${REST_PORT}/health"
echo " curl -X POST http://localhost:${REST_PORT}/sql \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '{\"sql\":\"SELECT 1+1 AS result\"}'"
echo ""
echo -e " ${CYN}Web console:${NC}"
echo " http://localhost:${REST_PORT}"
echo ""
echo -e " ${CYN}PostgreSQL wire (psql):${NC}"
echo " psql -h localhost -p ${PG_PORT}"
echo ""
echo -e " ${CYN}gRPC:${NC}"
echo " grpcurl -plaintext localhost:${GRPC_PORT} list"
echo ""
echo -e " ${CYN}docker run (standalone):${NC}"
echo " docker run -d --name ${CONTAINER_NAME} \\"
echo " -p ${PG_PORT}:5433 -p ${REST_PORT}:8080 -p ${GRPC_PORT}:9090 \\"
echo " -v ${VOLUME_NAME}:/var/lib/absdb \\"
echo " ${IMAGE_NAME}:${IMAGE_TAG}"
echo ""
echo -e " ${CYN}docker compose (from ${COMPOSE_DIR}):${NC}"
echo " docker compose up -d # start"
echo " docker compose down # stop"
echo " docker compose logs -f # follow logs"
echo " docker compose pull # update image"
echo ""
echo -e " ${CYN}Container management:${NC}"
echo " docker ps # check running containers"
echo " docker logs ${CONTAINER_NAME} # view logs"
echo " docker logs ${CONTAINER_NAME} -f # follow logs"
echo " docker exec -it ${CONTAINER_NAME} sh # open shell inside container"
echo " docker stop ${CONTAINER_NAME} # stop"
echo " docker start ${CONTAINER_NAME} # start"
echo " docker restart ${CONTAINER_NAME} # restart"
echo ""
echo -e " ${CYN}Persistent volume:${NC}"
echo " docker volume ls # list volumes"
echo " docker volume inspect ${VOLUME_NAME} # inspect volume"
echo " docker volume rm ${VOLUME_NAME} # WARNING: deletes all data"
echo ""
echo -e " ${CYN}Update to new version:${NC}"
echo " docker pull ${IMAGE_NAME}:latest"
echo " docker stop ${CONTAINER_NAME} && docker rm ${CONTAINER_NAME}"
echo " # Re-run docker run command above (data is preserved in volume)"
echo ""
# ── Production vs Testing prompt ─────────────────────────────────────────────
if [ -t 0 ] && [ -t 1 ]; then
echo ""
echo -e " ${BOLD}Is this Docker installation for Production or Testing/Evaluation?${NC}"
echo ""
echo -e " ${CYN}1)${NC} Production — container starts clean, no sample data"
echo -e " ${CYN}2)${NC} Testing — optionally generate tier-sized sample data"
echo ""
INSTALL_TYPE=""
while true; do
read -rp " Enter choice [1/2] (default: 1): " _it
_it="${_it:-1}"
case "${_it}" in
1|production|prod) INSTALL_TYPE="production"; break ;;
2|testing|test) INSTALL_TYPE="testing"; break ;;
*) echo " Enter 1 or 2." ;;
esac
done
if [ "${INSTALL_TYPE}" = "testing" ]; then
echo ""
read -rp " Generate sample/test data inside the container? [y/N]: " _wd
_wd="${_wd:-n}"
if [[ "${_wd,,}" == "y" || "${_wd,,}" == "yes" ]]; then
echo ""
echo -e " ${BOLD}Select tier size:${NC}"
echo -e " ${CYN}1)${NC} Community — ${YLW}2 GB${NC}"
echo -e " ${CYN}2)${NC} SME — ${YLW}3 GB${NC}"
echo -e " ${CYN}3)${NC} Professional — ${YLW}10 GB${NC}"
echo -e " ${CYN}4)${NC} Enterprise — ${YLW}20 GB${NC}"
echo ""
DATA_TIER=""
while true; do
read -rp " Enter tier [1-4 or name]: " _dt
_dt="${_dt,,}"
case "${_dt}" in
1|community) DATA_TIER="community"; break ;;
2|sme) DATA_TIER="sme"; break ;;
3|pro|professional) DATA_TIER="pro"; break ;;
4|enterprise) DATA_TIER="enterprise"; break ;;
*) echo " Enter 1–4 or community/sme/pro/enterprise." ;;
esac
done
echo ""
echo " Generating ${DATA_TIER} test data inside container..."
echo " This may take several minutes for larger tiers."
# Wait for container to be fully ready
sleep 3
docker exec "${CONTAINER_NAME}" \
python3 /app/tools/gen_test_data.py \
--tier "${DATA_TIER}" \
--host 127.0.0.1 \
--port 5433 \
--dbname absdb \
--drop-existing \
2>/dev/null \
|| echo " [WARN] Data gen failed — ensure container is running."
echo " To regenerate: docker exec ${CONTAINER_NAME} python3 /app/tools/gen_test_data.py --tier ${DATA_TIER} --drop-existing"
fi
else
echo " Production install — container starts clean."
echo " To generate test data later:"
echo " docker exec ${CONTAINER_NAME} python3 /app/tools/gen_test_data.py"
fi
fi
echo -e "${BOLD} Next steps:${NC}"
echo ""
echo -e " ${YLW}1. Host firewall:${NC} Docker maps container ports to the host."
echo -e " If your host has a firewall, ensure these ports are open for remote access:"
echo -e " ${DIM}PostgreSQL wire:${NC} ${PG_PORT} ${DIM}REST API + Web console:${NC} ${REST_PORT}"
echo -e " ${DIM}gRPC / HTTP/2:${NC} ${GRPC_PORT} ${DIM}Redis RESP3:${NC} 6379"
echo ""
echo -e " ${YLW}2. Open the web management console:${NC}"
echo -e " ${CYN}http://localhost:${REST_PORT}/console${NC}"
echo ""
echo -e " ${YLW}3. Enable TLS for production:${NC}"
echo -e " See ${DIM}https://absolutedb.com/docs/tls${NC}"
echo ""
echo -e " ${CYN}Uninstall:${NC}"
echo " bash install-docker.sh --uninstall"
echo ""
echo -e " ${CYN}Docs:${NC} https://absolutedb.com/docs/docker"
echo -e " ${CYN}Support:${NC} https://absolutedb.com/support"
echo ""