Index: /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/ARCHITECTURE.md	(working copy)
@@ -42,10 +42,10 @@
             PgBouncer["PgBouncer (Global)"]
         end
 
-        subgraph Compute_Layer ["Stateless Compute"]
-            Logstash["Logstash"]
-            Dashboards["OpenSearch Dashboards"]
-            Grafana["Grafana"]
+        subgraph Compute_Layer ["Stateless Compute (Global Mode)"]
+            Logstash["Logstash (Global)"]
+            Dashboards["OpenSearch Dashboards (Global)"]
+            Grafana["Grafana (Global)"]
             Telegraf["Telegraf (Global Monitor)"]
         end
         
@@ -126,3 +126,47 @@
 
 * **Latency**: Low latency (<10ms) between nodes is required for synchronous replication.
 * **Quorum**: A minimum of 3 nodes is required to maintain Quorum for Etcd and OpenSearch Master election. (2-node clusters are possible but not capable of automatic failover).
+
+## 6. Service Deployment Modes
+
+Services are deployed in one of two modes optimized for a 3-5 node cluster:
+
+### Global Mode (Run on Every Node)
+
+| Service | Rationale |
+|---------|----------|
+| **nginx** | Ensures web access via any node/VIP |
+| **opensearch-dashboards** | Local proxy access (avoids cross-node 502s) |
+| **grafana** | Local proxy access |
+| **logstash** | Syslog (port 514) must be available on all nodes |
+| **telegraf** | Monitors Docker on each host |
+| **haproxy** | Database load balancer on each node |
+| **pgbouncer** | Connection pooling on each node |
+
+### Replicated Mode (N instances, distributed)
+
+| Service | Replicas | Rationale |
+|---------|----------|----------|
+| **opensearch** | 3 (max 1/node) | Stateful cluster; uses Host Networking |
+| **timescaledb** | 3 (max 1/node) | Patroni HA; uses Host Networking |
+| **etcd** | 3 (1 per node) | Raft consensus; dynamically generated |
+
+> **Note**: Stateless UI services use `mode: global` with `ports.mode: host` to ensure Nginx on any node can reach a local backend via `host.docker.internal`.
+
+## 7. Firewall Considerations
+
+### Important: Docker Zone Management
+
+Docker automatically manages firewall rules for its bridge interfaces (`docker0`, `docker_gwbridge`). **Do NOT manually assign these interfaces to firewalld zones** (e.g., `trusted` or `public`). This conflicts with Docker's internal zone management and causes startup failures.
+
+**Correct Approach**:
+* Open required ports in the `public` zone.
+* Let Docker manage its own interfaces.
+
+```bash
+# Example: Open AMP ports (run on ALL nodes)
+firewall-cmd --permanent --zone=public --add-port=9200/tcp  # OpenSearch
+firewall-cmd --permanent --zone=public --add-port=9300/tcp  # OpenSearch Transport
+firewall-cmd --permanent --zone=public --add-port=5601/tcp  # Dashboards
+firewall-cmd --reload
+```
Index: /branches/amp_4_0/platform/tools/container/DEPLOYMENT.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/DEPLOYMENT.md	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/DEPLOYMENT.md	(working copy)
@@ -34,9 +34,10 @@
 firewall-cmd --add-port=5433/tcp --permanent # Database (Direct Patroni)
 firewall-cmd --add-port=2379-2380/tcp --permanent # Etcd
 firewall-cmd --add-port=8008/tcp --permanent # Patroni API
-firewall-cmd --add-port=9200/tcp --permanent # OpenSearch REST
+firewall-cmd --add-port=9200/tcp --permanent # OpenSearch REST (Inter-node & Dashboards)
 firewall-cmd --add-port=9300/tcp --permanent # OpenSearch Transport (Cluster)
 firewall-cmd --add-port=5601/tcp --permanent # OpenSearch Dashboards
+# IMPORTANT: Traffic between nodes on Overlay (UDP 4789) and OpenSearch (9200) MUST be allowed!
 firewall-cmd --add-port=3000/tcp --permanent # Grafana
 firewall-cmd --add-port=514/tcp --permanent  # Logstash Syslog TCP
 firewall-cmd --add-port=514/udp --permanent  # Logstash Syslog UDP
@@ -45,6 +46,21 @@
 firewall-cmd --reload
 ```
 
+> ⚠️ **WARNING**: Do NOT manually add Docker interfaces (`docker0`, `docker_gwbridge`) to firewalld zones. Docker manages these interfaces itself. Manual zone assignments cause `ZONE_CONFLICT` errors that prevent Docker from starting.
+
+### System Tuning (On ALL Nodes)
+
+OpenSearch requires increased virtual memory. Run on each node:
+
+```bash
+# Using manage_amp.sh (recommended)
+./manage_amp.sh system_tune
+
+# Or manually:
+sudo sysctl -w vm.max_map_count=262144
+echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
+```
+
 ---
 
 ## 2. Docker Swarm Setup
@@ -85,8 +101,48 @@
 
 ---
 
-## 3. Deployment
+## 3. Offline Deployment (Air-Gapped Environments)
 
+If you've created an offline bundle using `./manage_amp.sh bundle` on a build machine with internet access, follow these steps:
+
+### Prerequisites
+
+1. **Complete Section 1**: Ensure all prerequisites (hardware, network, firewall rules) are met on all nodes.
+2. **Complete Section 2**: Set up Docker Swarm cluster (init, join workers, promote to managers).
+
+### On the Offline Target Machine (Manager Node)
+
+1. **Transfer Files**: Copy these files to the offline machine:
+   * `amp_offline_bundle.tar.gz`
+   * `tar-bootstrap.rpm` (for minimal Rocky Linux installations)
+
+2. **Install tar** (if not present):
+
+   ```bash
+   rpm -ivh tar-bootstrap.rpm
+   ```
+
+3. **Extract Bundle**:
+
+   ```bash
+   tar -xf amp_offline_bundle.tar.gz
+   cd amp_offline_bundle
+   ```
+
+4. **Load Offline Bundle**:
+
+   ```bash
+   ./manage_amp.sh load_offline
+   ```
+
+   This installs all dependencies (Docker, rsync, keepalived, Java, Python) and loads Docker images into the local registry.
+
+5. **Continue with Standard Deployment**: After `load_offline` completes successfully, **jump to Section 4 below** (starting from Step 0: Configure VIP).
+
+---
+
+## 4. Standard Online Deployment
+
 All deployment actions are handled by the `manage_amp.sh` script on **Node 1 (Manager)**.
 
 ### Step 0: Configure Virtual IP (VIP) for HA
@@ -210,3 +266,94 @@
     ```bash
     ./manage_amp.sh deploy --auto
     ```
+
+### Common Issues
+
+| Symptom | Cause | Solution |
+|---------|-------|----------|
+| `502 Bad Gateway` on some requests | Dashboards not running on all nodes | Ensure `mode: global` in `stack.yml` |
+| `ZONE_CONFLICT` Docker crash | Manual firewalld zone assignment | Remove Docker interfaces from manual zones |
+| `invalid mount config` | Missing log directory on node | Create `/var/log/amp/opensearch` on all nodes |
+| `ECONNREFUSED` to OpenSearch | Firewall blocking port 9200 | Open port 9200 on all nodes |
+
+---
+
+## Appendix A: Docker Images
+
+The following Docker images are bundled/used by AMP:
+
+| Service | Image | Description |
+|---------|-------|-------------|
+| opensearch | `opensearchproject/opensearch` | Search and analytics engine |
+| opensearch-dashboards | `opensearchproject/opensearch-dashboards` | Visualization UI |
+| timescaledb | Custom build (Patroni) | Time-series database with HA |
+| pgbouncer | `edoburu/pgbouncer` | Connection pooling |
+| grafana | `grafana/grafana` | Monitoring dashboards |
+| telegraf | `telegraf` | Metrics collection agent |
+| logstash | `opensearchproject/logstash-oss-with-opensearch-output-plugin` | Log ingestion |
+| nginx | `nginx` | Reverse proxy |
+| etcd | `quay.io/coreos/etcd` | Distributed key-value store |
+| haproxy | `haproxy` | Database load balancer |
+| registry | `registry` | Local Docker registry |
+| busybox | `busybox` | Utility container |
+| rocky | `rockylinux` | Base OS image |
+
+---
+
+## Appendix B: RPM Packages (Offline Bundle)
+
+The offline bundle includes these packages and their dependencies:
+
+| Package | Purpose |
+|---------|---------|
+| docker-ce, docker-ce-cli, containerd.io | Docker runtime |
+| docker-buildx-plugin, docker-compose-plugin | Docker plugins |
+| keepalived | VIP failover (VRRP) |
+| rsync | File synchronization |
+| python3 | Scripting |
+| java-17-openjdk | OpenSearch security tools |
+| tar | Archive extraction |
+| openssl, httpd-tools | Certificate generation |
+| curl, jq | API calls and JSON parsing |
+| bind-utils | DNS tools (dig, nslookup) |
+| iputils | Network tools (ping) |
+| net-tools | Network debugging (netstat, ifconfig) |
+
+---
+
+## Appendix C: Service Deployment Modes
+
+| Service | Mode | Port | Rationale |
+|---------|------|------|-----------|
+| nginx | global | 80, 443 | Web access on every node |
+| opensearch-dashboards | global | 5601 | Local proxy access |
+| grafana | global | 3000 | Local proxy access |
+| logstash | global | 514 | Syslog on all nodes |
+| telegraf | global | - | Docker monitoring per node |
+| haproxy | global | 5432 | Database LB per node |
+| pgbouncer | global | - | Connection pooling |
+| opensearch | replicated (3) | 9200, 9300 | Stateful cluster |
+| timescaledb | replicated (3) | 5433 | Patroni HA cluster |
+| etcd | replicated (3) | 2379-2380 | Raft consensus |
+
+---
+
+## Appendix D: Default Ports
+
+| Port | Protocol | Service | Notes |
+|------|----------|---------|-------|
+| 80 | TCP | Nginx (HTTP) | Redirects to HTTPS |
+| 443 | TCP | Nginx (HTTPS) | Web UI entry point |
+| 514 | TCP/UDP | Logstash | Syslog ingestion |
+| 2377 | TCP | Docker Swarm | Cluster management |
+| 2379-2380 | TCP | Etcd | Cluster coordination |
+| 3000 | TCP | Grafana | Monitoring UI |
+| 4789 | UDP | Docker Overlay | Container networking |
+| 5000 | TCP | Registry | Local image storage |
+| 5432 | TCP | HAProxy | Database (via LB) |
+| 5433 | TCP | TimescaleDB | Direct Patroni access |
+| 5601 | TCP | Dashboards | OpenSearch UI |
+| 7946 | TCP/UDP | Docker Swarm | Node communication |
+| 8008 | TCP | Patroni | Health API |
+| 9200 | TCP | OpenSearch | REST API |
+| 9300 | TCP | OpenSearch | Cluster transport |
Index: /branches/amp_4_0/platform/tools/container/OFFLINE_GUIDE.md
===================================================================
--- /branches/amp_4_0/platform/tools/container/OFFLINE_GUIDE.md	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/OFFLINE_GUIDE.md	(working copy)
@@ -0,0 +1,163 @@
+# Offline Build & Installation Guide
+
+This guide details the process of building an offline installation bundle for the AMP platform on an internet-connected machine and deploying it on an air-gapped (offline) environment.
+
+## 1. Prerequisites (Build Machine)
+
+The build machine is used to download all necessary system packages (RPMs) and Docker images.
+
+* **OS**: Rocky Linux 9 / RHEL 9
+* **Internet Access**: Required
+* **Tools**:
+  * `dnf` (Standard package manager)
+  * `docker` (Installed and running)
+
+**Setup Build Machine**:
+Before creating the bundle, you must ensure Docker is installed and running on this machine (so it can pull the images).
+
+1. **Run the Installer**:
+
+    ```bash
+    ./install_prerequisites.sh
+    ```
+
+2. **Verify Docker**:
+
+    ```bash
+    docker ps
+    ```
+
+    *If this command fails, ensure the service is started (`systemctl start docker`) and your user has permissions.*
+
+## 2. Creating the Offline Bundle
+
+1. **Navigate to the Container Directory**:
+
+    ```bash
+    cd platform/tools/container/
+    ```
+
+2. **Run the Bundle Command**:
+    This script will download required RPMs (Docker, Java, Python, `net-tools`, etc.) and save all Docker images into a compressed archive.
+
+    ```bash
+    ./manage_amp.sh bundle
+    ```
+
+    > [!NOTE]
+    > Docker must be running. The script pulls/builds all images before saving them.
+
+3. **Verify Output**:
+    After the process completes, you will have two files:
+
+    ```bash
+    ls -lh amp_offline_bundle.tar.gz tar-bootstrap.rpm
+    ```
+
+    | File | Purpose |
+    |------|---------|
+    | `amp_offline_bundle.tar.gz` | Main bundle containing RPMs, Docker images, scripts, and configs |
+    | `tar-bootstrap.rpm` | Standalone `tar` package for minimal installs that lack `tar` |
+
+    > [!TIP]
+    > The bundle also includes a copy of `.env` from your build machine. You may need to update it for the target environment after extraction.
+
+## 3. Transferring to Offline Environment
+
+Copy **both** files to your target offline server using a USB drive, SCP (if a private link exists), or any other secure file transfer method:
+
+* `amp_offline_bundle.tar.gz`
+* `tar-bootstrap.rpm` (needed if target system has no `tar` installed)
+
+## 4. Installation (Target Offline Machine)
+
+1. **Install `tar` (if needed)**:
+
+    On minimal installations that don't have `tar`:
+
+    ```bash
+    sudo rpm -ivh tar-bootstrap.rpm
+    ```
+
+2. **Extract the Bundle**:
+
+    ```bash
+    tar -xf amp_offline_bundle.tar.gz
+    cd amp_offline_bundle
+    ```
+
+3. **Review Environment Configuration** (Optional but Recommended):
+
+    The bundle includes a `.env` file from the build machine. Review and update it for your target environment:
+
+    ```bash
+    vi .env
+    ```
+
+    Key variables to check:
+    * `AMP_DOMAIN_OR_IP` - Should match target node IP
+    * `POSTGRES_PASSWORD` / `OPENSEARCH_INITIAL_ADMIN_PASSWORD` - Credentials
+
+4. **Load Dependencies**:
+    This step installs system prerequisites (using the downloaded RPMs) and imports the Docker images into the local daemon.
+
+    ```bash
+    ./manage_amp.sh load_offline
+    ```
+
+    > [!NOTE]
+    > This may take several minutes as it extracts and loads large Docker images (~5-10GB).
+
+5. **Deploy AMP Cluster**:
+    Once the dependencies are loaded, deploy the cluster using the standard deployment command.
+
+    ```bash
+    ./manage_amp.sh deploy --auto
+    ```
+
+## 5. Multi-Node Offline Deployment
+
+For multi-node clusters, repeat the following on each **worker node**:
+
+1. Copy the bundle to the worker node
+2. Extract and run `load_offline` to install Docker and load images
+3. Configure insecure registry to point to the manager node:
+
+    ```bash
+    ./manage_amp.sh config_registry <MANAGER_IP>
+    ```
+
+4. Join the node to the Swarm (using the join token from the manager)
+
+## 6. Verification
+
+* **Check Services**:
+
+    ```bash
+    docker service ls
+    ```
+
+    Ensure all services show `Replicas 1/1` (or `3/3` for HA components).
+
+* **Check Logs**:
+  * **OpenSearch** logs are stored persistently on the host at `/var/log/amp/opensearch` (or your configured `AMP_LOG_ROOT`).
+  * Verify this directory exists and contains logs (e.g., `cluster-name.log`).
+
+* **Access UI**:
+    Open `https://<NODE_IP>/` in your browser.
+
+## 7. Troubleshooting
+
+| Issue | Resolution |
+|-------|------------|
+| `tar: command not found` | Install tar-bootstrap.rpm first |
+| Docker not starting | Check `systemctl status docker` and logs |
+| Images not loading | Ensure `images/amp_images.tar.gz` exists in bundle |
+| Registry push failures | Verify Docker is running and registry container is up |
+
+**Debugging Tools**:
+The bundle includes `net-tools` which provides `netstat`, `ifconfig`, and other utilities for network troubleshooting.
+
+```bash
+netstat -tulnp | grep LISTEN
+```
Index: /branches/amp_4_0/platform/tools/container/install_prerequisites.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(revision 2857)
+++ /branches/amp_4_0/platform/tools/container/install_prerequisites.sh	(working copy)
@@ -67,34 +67,50 @@
         fi
         
     elif [ "$PKG_MANAGER" == "dnf" ]; then
-        # RHEL/Rocky Specific Logic using Custom Installers
+        # RHEL/Rocky Specific Logic
         SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
         SETUP_DIR="$SCRIPT_DIR/services/setup"
+        LOCAL_PKG_DIR="$SCRIPT_DIR/packages"
+
+        # 0. Offline Bulk Install (Priority)
+        if [ -d "$LOCAL_PKG_DIR" ] && [ "$(ls -A $LOCAL_PKG_DIR/*.rpm 2>/dev/null)" ]; then
+             echo "📦 Found local RPM packages in $LOCAL_PKG_DIR. Performing offline bulk install..."
+             sudo dnf install -y --disablerepo='*' "$LOCAL_PKG_DIR"/*.rpm
+             
+             # Enable Docker if installed
+             if check_cmd docker; then
+                 sudo systemctl start docker
+                 sudo systemctl enable docker
+             fi
+             echo "✅ Offline packages installed."
+        fi
         
-        # Install Python 3.13 via custom script
+        # 1. Individual Checks (Fallbacks for Online Mode)
+        
+        # Install Python 3 via custom script or dnf
         if [ -f "$SETUP_DIR/install_python.sh" ]; then
              echo "Invoking custom Python installer (install_python.sh)..."
              sudo bash "$SETUP_DIR/install_python.sh"
-        else
-             echo "⚠️  Custom install_python.sh not found at $SETUP_DIR/install_python.sh. Falling back to dnf."
+        elif ! check_cmd python3; then
+             echo "Installing Python3..."
              sudo dnf install -y python3
         fi
         
-        # Install Java via custom script
+        # Install Java via custom script or dnf
         if [ -f "$SETUP_DIR/install_java.sh" ]; then
              echo "Invoking custom Java installer (install_java.sh)..."
              sudo bash "$SETUP_DIR/install_java.sh"
-        else
-             echo "⚠️  Custom install_java.sh not found at $SETUP_DIR/install_java.sh. Falling back to dnf."
+        elif ! check_cmd java; then
+             echo "Installing Java (OpenJDK)..."
              sudo dnf install -y java-17-openjdk
         fi
 
-        # Install curl via custom script
+        # Install curl via custom script or dnf
         if [ -f "$SETUP_DIR/install_curl.sh" ]; then
              echo "Invoking custom curl installer (install_curl.sh)..."
              sudo bash "$SETUP_DIR/install_curl.sh"
-        else
-             echo "⚠️  Custom install_curl.sh not found at $SETUP_DIR/install_curl.sh. Falling back to dnf."
+        elif ! check_cmd curl; then
+             echo "Installing curl..."
              sudo $PKG_MANAGER install -y curl
         fi
         
@@ -102,11 +118,9 @@
         if [ -f "$SETUP_DIR/install_keepalived.sh" ]; then
              echo "Invoking custom keepalived installer (install_keepalived.sh)..."
              sudo bash "$SETUP_DIR/install_keepalived.sh"
-        else
-             if ! check_cmd keepalived; then
-                 echo "Installing Keepalived..."
-                 sudo $PKG_MANAGER install -y keepalived
-             fi
+        elif ! check_cmd keepalived; then
+             echo "Installing Keepalived..."
+             sudo $PKG_MANAGER install -y keepalived
         fi
     fi
 
Index: /branches/amp_4_0/platform/tools/container/manage_amp.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/manage_amp.sh	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/manage_amp.sh	(working copy)
@@ -6,8 +6,64 @@
 ACTION=${1:-status}
 STACK_NAME="amp"
 
+# Global Image Definitions
+
+export TAG_OPENSEARCH="${TAG_OPENSEARCH:-3.4.0}"
+export TAG_PGBOUNCER="${TAG_PGBOUNCER:-v1.25.1-p0}"
+export TAG_GRAFANA="${TAG_GRAFANA:-11.5.0}"
+export TAG_TELEGRAF="${TAG_TELEGRAF:-1.36.4}"
+export TAG_LOGSTASH="${TAG_LOGSTASH:-8.9.0}"
+export TAG_NGINX="${TAG_NGINX:-stable-alpine}"
+export TAG_OS_DASHBOARDS="${TAG_OS_DASHBOARDS:-3.4.0}"
+export TAG_ETCD="${TAG_ETCD:-v3.5.17}"
+export TAG_HAPROXY="${TAG_HAPROXY:-3.3-alpine}"
+export TAG_REGISTRY="${TAG_REGISTRY:-3.0.0}"
+export TAG_BUSYBOX="${TAG_BUSYBOX:-1.37.0}"
+export TAG_ROCKY="${TAG_ROCKY:-9}"
+
+# Logging Configuration
+export AMP_LOG_ROOT="${AMP_LOG_ROOT:-/var/log/amp}"
+
+# Global Image Definitions
+# Format: "service_name:upstream_image"
+IMAGES=(
+    "opensearch:opensearchproject/opensearch:${TAG_OPENSEARCH}"
+    "timescaledb:custom_build" # Uses :latest by default for custom build
+    "pgbouncer:edoburu/pgbouncer:${TAG_PGBOUNCER}"
+    "grafana:grafana/grafana:${TAG_GRAFANA}"
+    "telegraf:telegraf:${TAG_TELEGRAF}"
+    "logstash:opensearchproject/logstash-oss-with-opensearch-output-plugin:${TAG_LOGSTASH}"
+    "nginx:nginx:${TAG_NGINX}"
+    "opensearch-dashboards:opensearchproject/opensearch-dashboards:${TAG_OS_DASHBOARDS}"
+    "etcd:quay.io/coreos/etcd:${TAG_ETCD}"
+    "haproxy:haproxy:${TAG_HAPROXY}"
+    "registry:registry:${TAG_REGISTRY}"
+    "busybox:busybox:${TAG_BUSYBOX}"
+    "rocky:rockylinux:${TAG_ROCKY}"
+)
+
 # REPO_ROOT Resolution
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+LOG_FILE="$SCRIPT_DIR/manage_amp.log"
+
+setup_logging() {
+    # If standard output is already redirected, do nothing to prevent infinite loops
+    if [ ! -t 1 ]; then
+        return
+    fi
+    
+    # Redirect stdout and stderr to tee, appending to LOG_FILE
+    exec > >(tee -a "$LOG_FILE") 2>&1
+    
+    echo "================================================================================"
+    echo "Starting execution at $(date)"
+    echo "Command: $0 $@"
+    echo "================================================================================"
+}
+
+# Initialize Logging
+setup_logging
+
 # Resolve SERVICES_DIR relative to the script
 SERVICES_DIR="$(cd "$SCRIPT_DIR/services" && pwd)"
 STACK_FILE="$SCRIPT_DIR/stack.yml"
@@ -62,7 +118,7 @@
 
         cat <<EOF >> "$output_file"
   etcd${idx}:
-    image: \${REGISTRY:-127.0.0.1:5000}/amp/etcd:latest
+    image: \${REGISTRY:-127.0.0.1:5000}/amp/etcd:\${TAG_ETCD}
     deploy:
       replicas: 1
       placement:
@@ -235,6 +291,21 @@
 AMP_OS_HOSTS_JSON='$os_hosts_json'
 AMP_OS_URL_LIST='$os_url_list'
 AMP_DB_JDBC_HOSTS=$db_hosts_csv
+
+# Version Tags
+TAG_OPENSEARCH=$TAG_OPENSEARCH
+TAG_PGBOUNCER=$TAG_PGBOUNCER
+TAG_GRAFANA=$TAG_GRAFANA
+TAG_TELEGRAF=$TAG_TELEGRAF
+TAG_LOGSTASH=$TAG_LOGSTASH
+TAG_NGINX=$TAG_NGINX
+TAG_OS_DASHBOARDS=$TAG_OS_DASHBOARDS
+TAG_ETCD=$TAG_ETCD
+TAG_HAPROXY=$TAG_HAPROXY
+TAG_REGISTRY=$TAG_REGISTRY
+TAG_BUSYBOX=$TAG_BUSYBOX
+TAG_ROCKY=$TAG_ROCKY
+AMP_LOG_ROOT=$AMP_LOG_ROOT
 $END_MARKER
 EF
 
@@ -245,6 +316,13 @@
     source "$ENV_FILE"
     set +a
 
+    # Create local log directory to ensure permissions are correct (UID 1000 for OpenSearch)
+    if [ ! -d "$AMP_LOG_ROOT/opensearch" ]; then
+        echo "Creating log directory: $AMP_LOG_ROOT/opensearch"
+        sudo mkdir -p "$AMP_LOG_ROOT/opensearch"
+        sudo chown -R 1000:1000 "$AMP_LOG_ROOT/opensearch"
+    fi
+
     # Generate stack.yml dynamically
     generate_stack_yaml "$NODES"
 }
@@ -303,7 +381,44 @@
         echo "✅ Docker restarted."
     else
         echo "✅ Registry configuration already exists."
+    fi
+}
+
+# Function: System Tuning (OpenSearch requirements)
+system_tune() {
+    echo "--- Applying System Tuning for OpenSearch ---"
+    
+    # Set vm.max_map_count (required for OpenSearch)
+    CURRENT=$(sysctl -n vm.max_map_count 2>/dev/null || echo "0")
+    REQUIRED=262144
+    
+    if [ "$CURRENT" -lt "$REQUIRED" ]; then
+        echo "Setting vm.max_map_count to $REQUIRED (current: $CURRENT)"
+        sudo sysctl -w vm.max_map_count=$REQUIRED
+        
+        # Make persistent across reboots
+        if ! grep -q "vm.max_map_count" /etc/sysctl.conf 2>/dev/null; then
+            echo "vm.max_map_count=$REQUIRED" | sudo tee -a /etc/sysctl.conf
+            echo "✅ Made persistent in /etc/sysctl.conf"
+        else
+            echo "✅ Already persistent in /etc/sysctl.conf"
+        fi
+    else
+        echo "✅ vm.max_map_count already set to $CURRENT (>= $REQUIRED)"
+    fi
+    
+    # Create log directory for OpenSearch (required for bind mount)
+    LOG_DIR="${AMP_LOG_ROOT:-/var/log/amp}/opensearch"
+    if [ ! -d "$LOG_DIR" ]; then
+        echo "Creating log directory: $LOG_DIR"
+        sudo mkdir -p "$LOG_DIR"
+        sudo chmod 777 "$LOG_DIR"
+        echo "✅ Created $LOG_DIR"
+    else
+        echo "✅ Log directory already exists: $LOG_DIR"
     fi
+    
+    echo "✅ System tuning complete."
 }
 
 # Function: Setup VIP (Integrated from configure_vip.sh)
@@ -679,40 +794,50 @@
     ensure_registry
     echo "--- Mirroring Images (Pull -> Tag -> Push) ---"
     
-    # Map service names to upstream images
-    # Format: "service_name:upstream_image"
-    IMAGES=(
-        "opensearch:opensearchproject/opensearch:2.11.0"
-        "timescaledb:custom_build" # Marker for custom build
-        "pgbouncer:edoburu/pgbouncer:latest"
-        "grafana:grafana/grafana:latest"
-        "telegraf:telegraf:latest"
-        "logstash:opensearchproject/logstash-oss-with-opensearch-output-plugin:latest"
-        "nginx:nginx:alpine"
-        "opensearch-dashboards:opensearchproject/opensearch-dashboards:2.11.0"
-        "etcd:quay.io/coreos/etcd:v3.5.11"
-        "haproxy:haproxy:alpine"
-    )
-
+    # Use global IMAGES array
     for INFO in "${IMAGES[@]}"; do
         SERVICE_NAME="${INFO%%:*}"
         UPSTREAM="${INFO#*:}"
-        LOCAL_TAG="$REGISTRY/amp/$SERVICE_NAME:latest"
-
-        echo "Processing $SERVICE_NAME..."
         
+        # Determine Tag and Source Image
         if [ "$SERVICE_NAME" == "timescaledb" ]; then
-            echo "  - Building Custom Image from services/timescaledb/Dockerfile.patroni..."
-            docker build -t $LOCAL_TAG -f "$SERVICES_DIR/timescaledb/Dockerfile.patroni" "$SERVICES_DIR/timescaledb"
+            TAG="latest"
+            SRC_IMAGE="amp/timescaledb:latest"
         else
+            TAG="${UPSTREAM##*:}"
+            SRC_IMAGE="$UPSTREAM"
+        fi
+        
+        LOCAL_TAG="$REGISTRY/amp/$SERVICE_NAME:$TAG"
+
+        echo "Processing $SERVICE_NAME (Tag: $TAG)..."
+        
+        # Check if image already exists locally (offline mode detection)
+        if docker image inspect "$SRC_IMAGE" &>/dev/null; then
+            echo "  - Image found locally (using pre-loaded image)"
+            echo "  - Tagging as $LOCAL_TAG"
+            docker tag "$SRC_IMAGE" "$LOCAL_TAG"
+        elif [ "$SERVICE_NAME" == "timescaledb" ]; then
+            echo "  - Building Custom Image..."
+            docker build -t "$LOCAL_TAG" -f "$SERVICES_DIR/timescaledb/Dockerfile.patroni" "$SERVICES_DIR/timescaledb" || {
+                echo "  ❌ Build failed. In offline mode, ensure amp/timescaledb:latest is pre-loaded."
+                continue
+            }
+        else
             echo "  - Pulling $UPSTREAM"
-            docker pull -q $UPSTREAM
+            docker pull -q "$UPSTREAM" || {
+                echo "  ❌ Pull failed. Trying to use local image..."
+                if ! docker image inspect "$UPSTREAM" &>/dev/null; then
+                    echo "  ❌ Image not found locally. Skipping."
+                    continue
+                fi
+            }
             echo "  - Tagging as $LOCAL_TAG"
-            docker tag $UPSTREAM $LOCAL_TAG
+            docker tag "$UPSTREAM" "$LOCAL_TAG"
         fi
         
         echo "  - Pushing to Registry..."
-        docker push -q $LOCAL_TAG
+        docker push -q "$LOCAL_TAG" || echo "  ⚠️ Push failed for $LOCAL_TAG"
         echo "  ✅ Done."
     done
 }
@@ -744,8 +869,8 @@
     TEMP_CERTS="/tmp/amp_certs_$$"
     mkdir -p "$TEMP_CERTS"
     
-    # Copy certs from volume using a temporary container
-    docker run --rm -v certs-vol:/certs -v "$TEMP_CERTS:/output" busybox sh -c "cp -r /certs/* /output/"
+    # Copy certs from volume using a temporary container (Use bundled busybox)
+    docker run --rm -v certs-vol:/certs -v "$TEMP_CERTS:/output" ${REGISTRY:-127.0.0.1:5000}/amp/busybox:${TAG_BUSYBOX} sh -c "cp -r /certs/* /output/"
     
     # Create Docker configs for each certificate file
     for cert_file in root-ca.pem node.pem node-key.pem admin.pem admin-key.pem; do
@@ -809,7 +934,8 @@
 
     # 2. Check if Certificates Exist
     echo "Checking for existing certificates..."
-    if docker run --rm -v certs-vol:/certs busybox sh -c '[ -z "$(ls -A /certs)" ]'; then
+    echo "Checking for existing certificates..."
+    if docker run --rm -v certs-vol:/certs ${REGISTRY:-127.0.0.1:5000}/amp/busybox:${TAG_BUSYBOX} sh -c '[ -z "$(ls -A /certs)" ]'; then
         echo "⚠️  Certificates not found in 'certs-vol'. Auto-running setup..."
         run_setup
     else
@@ -849,7 +975,9 @@
         -v "security-config-vol:/security-config:rw" \
         -e OPENSEARCH_INITIAL_ADMIN_PASSWORD="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}" \
         -e OPENSEARCH_JWT_SECRET="${OPENSEARCH_JWT_SECRET:-supersecretjwtkey}" \
-        rockylinux:9 \
+        -e OPENSEARCH_INITIAL_ADMIN_PASSWORD="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}" \
+        -e OPENSEARCH_JWT_SECRET="${OPENSEARCH_JWT_SECRET:-supersecretjwtkey}" \
+        ${REGISTRY:-127.0.0.1:5000}/amp/rocky:${TAG_ROCKY} \
         /bin/sh -c "dnf install -y openssl httpd-tools && chmod +x /setup/setup.sh && /setup/setup.sh /certs /security-config && chown -R 1000:1000 /certs && chown -R 1000:1000 /security-config" &
     
     PID=$!
@@ -920,35 +1048,33 @@
 run_configurator() {
     echo "--- Running Configurator (OpenSearch Setup) ---"
     
-    # Needs to run on the overlay network to reach 'opensearch' service
-    # and mount certs to create the security roles using curl
-    
     check_swarm
     
-    # We use docker run attached to the overlay network
-    # Note: overlay networks are only attachable if 'attachable: true' is set in stack.yml (it is).
+    # Find a running OpenSearch container (it already has curl installed)
+    CONTAINER_ID=$(docker ps --format '{{.ID}} {{.Names}}' | grep "amp_opensearch" | grep -v "dashboards" | awk '{print $1}' | head -n 1)
     
-    echo "Waiting for OpenSearch to be reachable..."
-    # A simple check before running configurator could go here, or let the script fail if not ready.
-
-    docker run --rm --name amp-configurator-ephemeral \
-        --network=${STACK_NAME}_amp-overlay \
+    if [ -z "$CONTAINER_ID" ]; then
+        echo "❌ No running OpenSearch container found. Is the stack deployed?"
+        exit 1
+    fi
+    
+    echo "Using OpenSearch container: $CONTAINER_ID"
+    
+    # Copy configuration scripts and files to the container
+    echo "Copying configuration files to container..."
+    docker cp "$SERVICES_DIR/setup/configure_opensearch.sh" "$CONTAINER_ID:/tmp/configure_opensearch.sh"
+    docker cp "$SERVICES_DIR/opensearch/amplog_template.json" "$CONTAINER_ID:/usr/share/opensearch/config/amplog_template.json"
+    docker cp "$SERVICES_DIR/opensearch-dashboards/export.ndjson" "$CONTAINER_ID:/usr/share/opensearch/config/export.ndjson"
+    
+    # Run the configurator script inside the OpenSearch container
+    echo "Running configurator..."
+    docker exec \
         -e OPENSEARCH_INITIAL_ADMIN_PASSWORD="${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-admin}" \
-        -e OPENSEARCH_URL="https://${AMP_DOMAIN_OR_IP}:9200" \
-        -e OPENSEARCH_DASHBOARDS_URL="https://opensearch-dashboards:5601" \
-        -v "certs-vol:/usr/share/opensearch/config/certs:ro" \
-        -v "$SERVICES_DIR/setup:/setup:ro" \
-        -v "$SERVICES_DIR/setup/install_curl.sh:/usr/local/bin/install_curl.sh" \
-        -v "$SERVICES_DIR/setup/configure_opensearch.sh:/usr/local/bin/configure_opensearch.sh" \
-        -v "$SERVICES_DIR/opensearch/amplog_template.json:/usr/share/opensearch/config/amplog_template.json" \
-        -v "$SERVICES_DIR/opensearch-dashboards/export.ndjson:/usr/share/opensearch/config/export.ndjson" \
-        rockylinux:9 \
-        /bin/sh -c "bash /usr/local/bin/install_curl.sh && bash /usr/local/bin/configure_opensearch.sh" &
-        
-    PID=$!
-    wait $PID
-
-        
+        -e OPENSEARCH_URL="https://127.0.0.1:9200" \
+        -e OPENSEARCH_DASHBOARDS_URL="https://127.0.0.1:5601" \
+        "$CONTAINER_ID" \
+        /bin/bash /tmp/configure_opensearch.sh
+    
     echo "✅ Configurator finished."
 }
 
@@ -1037,6 +1163,252 @@
     fi
 }
 
+# Function: Bundle for Offline Deployment
+bundle_offline() {
+    echo "--- Creating Offline Bundle ---"
+    
+    # 1. Check for DNF (Rocky/RHEL)
+    if ! command -v dnf &> /dev/null; then
+        echo "❌ 'dnf' not found. Please run this on a Rocky Linux/RHEL 9 system."
+        exit 1
+    fi
+    
+    BUNDLE_DIR="amp_offline_bundle"
+    mkdir -p "$BUNDLE_DIR/packages"
+    mkdir -p "$BUNDLE_DIR/images"
+    
+    echo "--- Step 1: Downloading RPMs ---"
+    # Ensure Docker repo is added for downloading
+    if [ ! -f /etc/yum.repos.d/docker-ce.repo ]; then
+        echo "Adding Docker Repo..."
+        sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+    fi
+
+    # List of required packages
+    # We include 'tar' and 'rsync' just in case.
+    # openssl and httpd-tools are needed for certificate generation during setup
+    # curl, jq, bind-utils, iputils are needed for configurator script\n    # net-tools provides netstat, ifconfig, etc. for debugging
+    PACKAGES="docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin keepalived rsync python3 java-17-openjdk tar openssl httpd-tools curl jq bind-utils iputils net-tools"
+    
+    echo "Downloading: $PACKAGES"
+    sudo dnf download --resolve --alldeps --destdir="$BUNDLE_DIR/packages" $PACKAGES
+    
+    echo "✅ RPMs downloaded to $BUNDLE_DIR/packages"
+    
+    echo "--- Step 2: Preparing Docker Images ---"
+    
+    IMAGE_LIST=""
+    
+    # Helper for retrying commands
+    retry_command() {
+        local -i max_attempts=5
+        local -i attempt=1
+        local -i wait_time=2
+        local cmd="$@"
+        
+        while [ $attempt -le $max_attempts ]; do
+            echo "   Executing: $cmd (Attempt $attempt/$max_attempts)"
+            if eval "$cmd"; then
+                return 0
+            else
+                echo "   ❌ Command failed. Retrying in ${wait_time}s..."
+                sleep $wait_time
+                attempt=$((attempt + 1))
+                wait_time=$((wait_time * 2))
+            fi
+        done
+        echo "   ❌ Command failed after $max_attempts attempts."
+        return 1
+    }
+
+    # Pull images first
+    for INFO in "${IMAGES[@]}"; do
+        SERVICE_NAME="${INFO%%:*}"
+        UPSTREAM="${INFO#*:}"
+        
+        echo "Processing $SERVICE_NAME..."
+        
+        if [ "$SERVICE_NAME" == "timescaledb" ]; then
+             echo "  - Building Custom Image..."
+             retry_command "docker build -t amp/timescaledb:latest -f \"$SERVICES_DIR/timescaledb/Dockerfile.patroni\" \"$SERVICES_DIR/timescaledb\"" || exit 1
+             IMAGE_LIST="amp/timescaledb:latest $IMAGE_LIST"
+        else
+             echo "  - Pulling $UPSTREAM"
+             retry_command "docker pull -q $UPSTREAM" || exit 1
+             # Retag to standard format if needed, but for save/load we just need the image present
+             # We will save the exact upstream tag
+             IMAGE_LIST="$UPSTREAM $IMAGE_LIST"
+        fi
+    done
+    
+    echo "--- Step 3: Saving Docker Images to Archive ---"
+    # We save all images into a single massive tarball
+    echo "Saving images: $IMAGE_LIST"
+    docker save -o "$BUNDLE_DIR/images/amp_images.tar" $IMAGE_LIST
+    
+    # Compress the tarball to save space (gzip)
+    echo "Compressing image archive..."
+    gzip "$BUNDLE_DIR/images/amp_images.tar"
+    
+    echo "✅ Images saved to $BUNDLE_DIR/images/amp_images.tar.gz"
+    
+    echo "--- Step 4: Finalizing Bundle ---"
+    cp manage_amp.sh "$BUNDLE_DIR/"
+    cp install_prerequisites.sh "$BUNDLE_DIR/"
+    cp stack.yml.template "$BUNDLE_DIR/"
+    cp -r services "$BUNDLE_DIR/"
+    
+    # Copy .env if it exists
+    if [ -f ".env" ]; then
+        cp .env "$BUNDLE_DIR/.env"
+        echo "✅ Copied .env (preserves configuration from build machine)"
+    fi
+    
+    # Create final archive
+    FINAL_ARCHIVE="amp_offline_bundle.tar.gz"
+    tar -czf "$FINAL_ARCHIVE" "$BUNDLE_DIR"
+    
+    # Copy tar RPM alongside bundle for minimal installs (tar needed to extract bundle)
+    TAR_RPM=$(find "$BUNDLE_DIR/packages" -name "tar-*.rpm" | head -1)
+    if [ -n "$TAR_RPM" ]; then
+        cp "$TAR_RPM" ./tar-bootstrap.rpm
+        echo "✅ Copied tar RPM for bootstrap: tar-bootstrap.rpm"
+    fi
+    
+    echo "✅ Bundle Creation Complete: $FINAL_ARCHIVE"
+    echo ""
+    echo "Transfer these files to the offline machine:"
+    echo "  - $FINAL_ARCHIVE"
+    echo "  - tar-bootstrap.rpm (for minimal installs without tar)"
+    echo ""
+    echo "On the offline machine, run:"
+    echo "  # If tar is not installed:"
+    echo "  rpm -ivh tar-bootstrap.rpm"
+    echo "  # Then extract and deploy:"
+    echo "  tar -xf $FINAL_ARCHIVE"
+    echo "  cd $BUNDLE_DIR"
+    echo "  ./manage_amp.sh load_offline"
+    echo "  ./manage_amp.sh deploy --auto"
+}
+
+# Function: Load Offline Bundle
+load_offline() {
+    echo "--- Loading Offline Bundle ---"
+    
+    # 1. Install RPMs via install_prerequisites override or custom logic
+    # We can just call install_prerequisites.sh which we will modify to look for local packages
+    
+    if [ -d "packages" ]; then
+        echo "Found local packages directory."
+        
+        echo "Installing Dependencies from local RPMs..."
+        # Use --skip-broken to handle version conflicts and --allowerasing for package replacements
+        sudo dnf install -y packages/*.rpm --disablerepo='*' --skip-broken --allowerasing
+        
+        # Explicitly check and install Docker if it failed
+        if ! command -v docker &> /dev/null; then
+            echo "Docker not found, installing Docker packages explicitly..."
+            sudo dnf install -y \
+                packages/containerd.io-*.rpm \
+                packages/docker-ce-cli-*.rpm \
+                packages/docker-ce-*.rpm \
+                packages/docker-buildx-plugin-*.rpm \
+                packages/docker-compose-plugin-*.rpm \
+                --disablerepo='*' --allowerasing || {
+                    echo "❌ Docker installation failed. Please check for conflicts."
+                    echo "Try running: sudo dnf install -y packages/docker-*.rpm packages/containerd.io-*.rpm --allowerasing"
+                    exit 1
+                }
+        fi
+        
+        # Verify Docker is now available
+        if ! command -v docker &> /dev/null; then
+            echo "❌ Docker installation failed. Cannot proceed without Docker."
+            exit 1
+        fi
+        
+        # Start Docker
+        sudo systemctl enable --now docker || {
+            echo "❌ Failed to start Docker service"
+            exit 1
+        }
+        
+        # Wait for Docker to be ready
+        echo "Waiting for Docker to start..."
+        sleep 5
+        
+        echo "✅ Dependencies Installed."
+    else
+        echo "⚠️  'packages' directory not found. Using standard install_prerequisites..."
+        ./install_prerequisites.sh
+    fi
+    
+    # 2. Load Docker Images
+    if [ -f "images/amp_images.tar.gz" ]; then
+        echo "Loading Docker Images (this may take time)..."
+        gunzip -c images/amp_images.tar.gz | docker load
+        
+        echo "✅ Images Loaded."
+        
+        # 3. Start Local Registry using BUNDLED image (not from Docker Hub)
+        echo "Starting Local Registry using bundled image..."
+        
+        # Stop any existing registry container first
+        docker rm -f registry 2>/dev/null || true
+        
+        # Use the bundled registry image (registry:3.0.0, not registry:2)
+        docker run -d -p 5000:5000 --restart=always --name registry "registry:${TAG_REGISTRY}" || {
+            echo "❌ Failed to start registry container"
+            echo "Trying alternative: using loaded image directly..."
+            docker run -d -p 5000:5000 --restart=always --name registry registry:3.0.0 || {
+                echo "❌ Registry startup failed. Please check 'docker images | grep registry'"
+                exit 1
+            }
+        }
+        
+        # Wait for registry to be ready
+        echo "Waiting for registry to start..."
+        sleep 3
+        
+        # Verify registry is running
+        if ! docker ps | grep -q "registry"; then
+            echo "❌ Registry container is not running"
+            docker logs registry
+            exit 1
+        fi
+        echo "✅ Local Registry started."
+        
+        # 4. Retag and push images to Local Registry
+        echo "Retagging images for Local Registry..."
+        
+        for INFO in "${IMAGES[@]}"; do
+            SERVICE_NAME="${INFO%%:*}"
+            UPSTREAM="${INFO#*:}"
+            
+            # Determine Tag
+            if [ "$SERVICE_NAME" == "timescaledb" ]; then
+                TAG="latest"
+                SRC_IMAGE="amp/timescaledb:latest"
+            else
+                TAG="${UPSTREAM##*:}"
+                SRC_IMAGE="$UPSTREAM"
+            fi
+            
+            LOCAL_TAG="127.0.0.1:5000/amp/$SERVICE_NAME:$TAG"
+            
+            echo "  - $SRC_IMAGE -> $LOCAL_TAG"
+            docker tag "$SRC_IMAGE" "$LOCAL_TAG"
+            docker push "$LOCAL_TAG"
+        done
+        
+        echo "✅ Images pushed to local registry."
+        
+    else
+        echo "❌ images/amp_images.tar.gz not found!"
+        exit 1
+    fi
+}
+
 case $ACTION in
     init)
         init_swarm "$2"  # Pass optional advertise-addr as second argument
@@ -1077,6 +1449,15 @@
         shift
         setup_vip "$@"
         ;;
+    bundle)
+        bundle_offline
+        ;;
+    load_offline)
+        load_offline
+        ;;
+    system_tune)
+        system_tune
+        ;;
     rm|remove)
         rm_stack
         ;;
@@ -1084,7 +1465,7 @@
         docker stack services $STACK_NAME
         ;;
     *)
-        echo "Usage: $0 {init|build|setup|deploy|security_init|status|configurator|rm|vip}"
+        echo "Usage: $0 {init|build|bundle|load_offline|setup|deploy|security_init|status|configurator|system_tune|rm|vip}"
         echo "  vip commands: ./manage_amp.sh vip --vip <IP> --priority <INT> [--interface <IFACE>]"
         exit 1
 esac
Index: /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/services/nginx/conf.d/app.conf	(working copy)
@@ -43,23 +43,20 @@
     # Enable access logging for debugging
     access_log /var/log/nginx/access.log;
 
-    # Resolver for lazy resolution (Docker DNS)
-    resolver 127.0.0.11 valid=30s;
-
     # Set access_token cookie and redirect to clean URL without query param
     location /visualization {
         if ($arg_access_token) {
             add_header Set-Cookie "access_token=$arg_access_token; Path=/visualization/; HttpOnly";
             return 302 /visualization/;
         }
-        set $opensearch_dashboards "https://opensearch-dashboards:5601";
-        proxy_pass $opensearch_dashboards;
+        proxy_pass https://host.docker.internal:5601;
         proxy_http_version 1.1;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Authorization "Bearer $jwt_token";
+        proxy_ssl_verify off;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
         proxy_cache_bypass $http_upgrade;
@@ -68,14 +65,14 @@
     }
 
     location ^~ /visualization/ {
-        set $opensearch_dashboards "https://opensearch-dashboards:5601";
-        proxy_pass $opensearch_dashboards;
+        proxy_pass https://host.docker.internal:5601;
         proxy_http_version 1.1;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Authorization "Bearer $jwt_token";
+        proxy_ssl_verify off;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
         proxy_cache_bypass $http_upgrade;
@@ -84,14 +81,14 @@
     }
 
     location ~* ^/visualization/(app|api|public|built_assets)/ {
-        set $opensearch_dashboards "https://opensearch-dashboards:5601";
-        proxy_pass $opensearch_dashboards;
+        proxy_pass https://host.docker.internal:5601;
         proxy_http_version 1.1;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Authorization "Bearer $jwt_token";
+        proxy_ssl_verify off;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
         proxy_cache_bypass $http_upgrade;
Index: /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/services/opensearch/opensearch.yml	(working copy)
@@ -25,7 +25,7 @@
   - "CN=admin,O=OpenSearchAdmin,L=Bengaluru,ST=Karnataka,C=IN"
 
 plugins.security.nodes_dn:
-  - "CN=node-1,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN"
+  - "CN=node-*,O=OpenSearchNode,L=Bengaluru,ST=Karnataka,C=IN"
 
 plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
 plugins.security.allow_default_init_securityindex: true
Index: /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh	(revision 2857)
+++ /branches/amp_4_0/platform/tools/container/services/setup/configure_opensearch.sh	(working copy)
@@ -18,16 +18,20 @@
 log "Contents of /etc/resolv.conf:"
 cat /etc/resolv.conf
 
-log "Testing DNS resolution:"
-for HOST in opensearch amp_opensearch tasks.amp_opensearch tasks.opensearch; do
-    if nslookup "$HOST" >/dev/null 2>&1; then
-        log "  ✅ nslookup $HOST: SUCCESS"
-        nslookup "$HOST"
-    else
-        log "  ❌ nslookup $HOST: FAILED"
-    fi
-done
+# --- DNS DEBUGGING ---
+log "🔍 Debugging Network/DNS..."
+log "Contents of /etc/resolv.conf:"
+cat /etc/resolv.conf
+
+# Check loop only for Dashboards (on overlay)
+log "Testing DNS resolution for Dashboards:"
+if nslookup tasks.opensearch-dashboards >/dev/null 2>&1; then
+    log "  ✅ nslookup tasks.opensearch-dashboards: SUCCESS"
+else
+    log "  ❌ nslookup tasks.opensearch-dashboards: FAILED (Required for connectivity)"
+fi
 # ---------------------
+# ---------------------
 
 while [ $COUNT -lt $MAX_RETRIES ]; do
   # Capture output and exit code to debug failure
@@ -118,22 +122,65 @@
   esac
 
   if [ -z "$CURRENT_STATE" ]; then CURRENT_STATE="Unknown"; fi
-  log "Dashboards not ready yet (Current: $CURRENT_STATE). Sleeping 5s..."
-  sleep 5
-  COUNT=$((COUNT+1))
+    log "Dashboards not ready yet (Current: $CURRENT_STATE). Sleeping 5s..."
+    sleep 5
+    COUNT=$((COUNT+1))
 done
 
 if [ $COUNT -eq $MAX_RETRIES ]; then
-    log "❌ Timeout waiting for Dashboards."
+    log "❌ Timeout waiting for Dashboards (VIP)."
+    
+    # --- DIAGNOSTIC MODE ---
+    log "🔎 Starting Deep Network Diagnostics..."
+    
+    # Check Local Interface and MTU
+    log "--- Local Network Config ---"
+    ip addr | grep -E 'eth0|mtu'
+    
+    # Resolve all task IPs (Replica IPs)
+    log "--- Resolving Replica IPs ---"
+    REPLICA_IPS=$(nslookup tasks.opensearch-dashboards 2>/dev/null | grep Address | grep -v "#53" | awk '{print $2}')
+    
+    if [ -z "$REPLICA_IPS" ]; then
+        log "❌ Could not resolve tasks.opensearch-dashboards"
+    else
+        for IP in $REPLICA_IPS; do
+            log "👉 Probing Replica: $IP"
+            
+            # Ping Test
+            ping -c 2 -W 2 $IP >/dev/null 2>&1
+            if [ $? -eq 0 ]; then
+                log "   ✅ Ping: SUCCESS"
+            else
+                log "   ❌ Ping: FAILED (Possible Firewall/VXLAN Issue)"
+            fi
+            
+            # Curl Test (Connect only)
+            curl -v -k --connect-timeout 5 "https://$IP:5601/api/status" >/dev/null 2>&1
+            if [ $? -eq 0 ]; then
+                log "   ✅ Curl (Port 5601): SUCCESS"
+            else
+                log "   ❌ Curl (Port 5601): FAILED"
+            fi
+        done
+    fi
+    log "-----------------------------"
     exit 1
 fi
 
 log "Importing Saved Objects (Index Patterns, Dashboards)..."
 # Using -w %{http_code} to catch errors and -o to avoid dumping large JSON to logs
-HTTP_CODE=$(curl -s -k -u "$ADMIN_USER:$ADMIN_PASS" -o /tmp/import_res.json -w "%{http_code}" \
+# Set longer timeout (300s) for first-time import which can be slow
+log "Testing connectivity to $OPENSEARCH_DASHBOARDS_URL first..."
+curl -v -k -u "$ADMIN_USER:$ADMIN_PASS" --connect-timeout 60 "$OPENSEARCH_DASHBOARDS_URL/visualization/api/status" 2>&1 | tail -5
+
+log "Starting import (this may take several minutes)..."
+HTTP_CODE=$(curl -v -k -u "$ADMIN_USER:$ADMIN_PASS" -o /tmp/import_res.json -w "%{http_code}" \
+  --connect-timeout 60 \
+  --max-time 600 \
   -X POST "$OPENSEARCH_DASHBOARDS_URL/visualization/api/saved_objects/_import?overwrite=true" \
   -H "osd-xsrf: true" \
-  --form file=@/usr/share/opensearch/config/export.ndjson)
+  --form file=@/usr/share/opensearch/config/export.ndjson 2> /tmp/curl_debug.log)
 
 log "Import finished. HTTP Response: $HTTP_CODE"
 if [ "$HTTP_CODE" -eq 200 ]; then
@@ -141,7 +188,11 @@
   cat /tmp/import_res.json
 else
   log "❌ Saved Objects import FAILED with code $HTTP_CODE"
-  cat /tmp/import_res.json
+  log "Curl debug output:"
+  cat /tmp/curl_debug.log | tail -20
+  if [ -f /tmp/import_res.json ]; then
+    cat /tmp/import_res.json
+  fi
 fi
 
 log "Configuration Complete!"
Index: /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh	(revision 2857)
+++ /branches/amp_4_0/platform/tools/container/services/setup/install_curl.sh	(working copy)
@@ -1,21 +1,32 @@
 #!/bin/bash
 # install_curl.sh
 # Installs curl, resolving potential conflicts with curl-minimal on Rocky Linux.
+# Supports offline mode by checking if packages are already installed.
 
 set -e
 
 echo "--- Installing curl ---"
 
+# Check if already installed (offline mode support)
+if command -v curl &>/dev/null && command -v jq &>/dev/null; then
+    echo "✅ curl and jq already installed (skipping dnf)"
+    curl --version | head -1
+    exit 0
+fi
+
 if [ -f /etc/os-release ]; then
     . /etc/os-release
     if [[ "$ID" == "rocky" || "$ID" == "rhel" || "$ID" == "almalinux" || "$ID" == "centos" || "$ID" == "fedora" ]]; then
         echo "Detected RHEL-based distribution: $ID"
         # --allowerasing is key for Rocky Linux 9 to replace curl-minimal
-        dnf install -y epel-release
-        dnf install -y --allowerasing curl jq bind-utils iputils
+        dnf install -y epel-release 2>/dev/null || echo "epel-release not available (offline?)"
+        dnf install -y --allowerasing curl jq bind-utils iputils || {
+            echo "❌ Package installation failed. In offline mode, ensure packages are pre-installed."
+            exit 1
+        }
     elif [[ "$ID" == "debian" || "$ID" == "ubuntu" ]]; then
         echo "Detected Debian-based distribution: $ID"
-        apt-get update && apt-get install -y curl
+        apt-get update && apt-get install -y curl jq
     else
         echo "⚠️  Unsupported distribution: $ID"
         exit 1
@@ -26,4 +37,4 @@
 fi
 
 echo "✅ curl installation complete."
-curl --version
+curl --version | head -1
Index: /branches/amp_4_0/platform/tools/container/stack.yml.template
===================================================================
--- /branches/amp_4_0/platform/tools/container/stack.yml.template	(revision 2872)
+++ /branches/amp_4_0/platform/tools/container/stack.yml.template	(working copy)
@@ -2,6 +2,8 @@
   amp-overlay:
     driver: overlay
     attachable: true
+    driver_opts:
+      com.docker.network.driver.mtu: "1200"
   hostnet:
     external: true
     name: host
@@ -66,7 +68,7 @@
 services:
   # --- Core Storage (Pinned to Storage Node) ---
   opensearch:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch:${TAG_OPENSEARCH:-2.11.0}
     deploy:
       mode: replicated
       replicas: ${AMP_REPLICAS:-3}
@@ -108,7 +110,9 @@
     volumes:
       - opensearch-data:/usr/share/opensearch/data
       - security-config-vol:/usr/share/opensearch/config/opensearch-security-mount:ro
-      - opensearch-logs:/usr/share/opensearch/logs
+      - type: bind
+        source: ${AMP_LOG_ROOT:-/var/log/amp}/opensearch
+        target: /usr/share/opensearch/logs
     networks:
       - hostnet
 
@@ -195,7 +199,7 @@
 
 
   haproxy:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/haproxy:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/haproxy:${TAG_HAPROXY:-2.9-alpine}
     deploy:
       mode: global
 
@@ -207,7 +211,7 @@
 
   # --- Middleware ---
   pgbouncer:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/pgbouncer:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/pgbouncer:${TAG_PGBOUNCER:-1.21.0}
     deploy:
       mode: global
 
@@ -231,7 +235,7 @@
 
   # --- Frontend / Observability ---
   nginx:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/nginx:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/nginx:${TAG_NGINX:-1.25.3-alpine}
     deploy:
       mode: global
 
@@ -249,6 +253,8 @@
         published: ${AMP_NGINX_HTTP_PORT:-80}
         protocol: tcp
         mode: host
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
     configs:
       - source: nginx_app_conf
         target: /etc/nginx/conf.d/app.conf
@@ -262,15 +268,13 @@
       - type: bind
         source: /dev/null
         target: /etc/nginx/conf.d/default.conf
-    extra_hosts:
-      - "host.docker.internal:host-gateway"
     networks:
       - amp-overlay
 
   opensearch-dashboards:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch-dashboards:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/opensearch-dashboards:${TAG_OS_DASHBOARDS:-2.11.0}
     deploy:
-      replicas: ${AMP_REPLICAS:-2}
+      mode: global  # Ensures Dashboards runs on every node for local Nginx access
     environment:
       OPENSEARCH_HOSTS: '${AMP_OS_HOSTS_JSON}' # USE PHYSICAL IP ARRAY
       OPENSEARCH_SSL_VERIFICATIONMODE: certificate
@@ -296,10 +300,13 @@
     networks:
       - amp-overlay
     ports:
-      - "5601:5601"
+      - target: 5601
+        published: 5601
+        protocol: tcp
+        mode: host
 
   grafana:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/grafana:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/grafana:${TAG_GRAFANA:-10.2.3}
     deploy:
       mode: global
       restart_policy:
@@ -355,7 +362,7 @@
         protocol: tcp
 
   telegraf:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/telegraf:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/telegraf:${TAG_TELEGRAF:-1.29.1}
     user: root
     security_opt:
       - label=disable
@@ -385,9 +392,9 @@
       - hostnet
 
   logstash:
-    image: ${REGISTRY:-127.0.0.1:5000}/amp/logstash:latest
+    image: ${REGISTRY:-127.0.0.1:5000}/amp/logstash:${TAG_LOGSTASH:-8.11.1}
     deploy:
-      replicas: ${AMP_REPLICAS:-2}
+      mode: global  # Run on every node to ensure syslog collection from all hosts
     ports:
       - "514:5514/tcp"
       - "514:5514/udp"
@@ -430,4 +437,3 @@
     external: true
   security-config-vol:
     external: true
-  opensearch-logs:
