Skip to content

Commit c295235

Browse files
NotYuShengclaude
andcommitted
feat: add offline deployment support with image save/load scripts
Adds docker-compose.offline.yml and two helper scripts for deploying TracePcap in air-gapped environments: pull-and-save-images.sh (builds and exports all images as tars) and load-images.sh (loads them on the offline host). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 93fcd96 commit c295235

3 files changed

Lines changed: 296 additions & 0 deletions

File tree

docker-compose.offline.yml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# docker-compose.offline.yml
2+
#
3+
# Offline deployment variant — uses pre-built images loaded via scripts/load-images.sh
4+
# instead of building from source.
5+
#
6+
# Prerequisites:
7+
# 1. On an internet-connected machine: bash scripts/pull-and-save-images.sh
8+
# 2. Transfer images/, this file, and .env to the offline machine.
9+
# 3. On the offline machine: bash scripts/load-images.sh
10+
#
11+
# Start: docker compose -f docker-compose.offline.yml up -d
12+
# Stop: docker compose -f docker-compose.offline.yml down
13+
14+
services:
15+
# PostgreSQL Database
16+
postgres:
17+
image: postgres:15-alpine
18+
container_name: tracepcap-postgres
19+
environment:
20+
POSTGRES_DB: tracepcap
21+
POSTGRES_USER: tracepcap_user
22+
POSTGRES_PASSWORD: tracepcap_pass
23+
TZ: Asia/Singapore
24+
ports:
25+
- "5432:5432"
26+
volumes:
27+
- postgres_data:/var/lib/postgresql/data
28+
healthcheck:
29+
test: ["CMD-SHELL", "pg_isready -U tracepcap_user -d tracepcap"]
30+
interval: 10s
31+
timeout: 5s
32+
retries: 5
33+
networks:
34+
- tracepcap-network
35+
36+
# MinIO Object Storage
37+
minio:
38+
image: minio/minio:latest
39+
container_name: tracepcap-minio
40+
command: server /data --console-address ":9001"
41+
environment:
42+
MINIO_ROOT_USER: minioadmin
43+
MINIO_ROOT_PASSWORD: minioadmin
44+
TZ: Asia/Singapore
45+
ports:
46+
- "9000:9000" # API
47+
- "9001:9001" # Console
48+
volumes:
49+
- minio_data:/data
50+
healthcheck:
51+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
52+
interval: 30s
53+
timeout: 20s
54+
retries: 3
55+
networks:
56+
- tracepcap-network
57+
58+
# MinIO Client (mc) - Create bucket on startup
59+
minio-init:
60+
image: minio/mc:latest
61+
container_name: tracepcap-minio-init
62+
depends_on:
63+
- minio
64+
entrypoint: >
65+
/bin/sh -c "
66+
sleep 5;
67+
/usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin;
68+
/usr/bin/mc mb myminio/tracepcap-files --ignore-existing;
69+
/usr/bin/mc anonymous set public myminio/tracepcap-files;
70+
exit 0;
71+
"
72+
networks:
73+
- tracepcap-network
74+
75+
# Spring Boot Backend (pre-built image)
76+
backend:
77+
image: tracepcap-backend:latest
78+
container_name: tracepcap-backend
79+
environment:
80+
SPRING_PROFILES_ACTIVE: dev
81+
DATABASE_URL: jdbc:postgresql://postgres:5432/tracepcap
82+
DATABASE_USERNAME: tracepcap_user
83+
DATABASE_PASSWORD: tracepcap_pass
84+
MINIO_ENDPOINT: http://minio:9000
85+
MINIO_ACCESS_KEY: minioadmin
86+
MINIO_SECRET_KEY: minioadmin
87+
MINIO_BUCKET: tracepcap-files
88+
APP_MEMORY_MB: ${APP_MEMORY_MB:-2048}
89+
LLM_API_BASE_URL: ${LLM_API_BASE_URL:-https://api.openai.com/v1}
90+
LLM_API_KEY: ${LLM_API_KEY:-your-api-key-here}
91+
LLM_MODEL: ${LLM_MODEL:-gpt-4}
92+
LLM_TEMPERATURE: ${LLM_TEMPERATURE:-0.7}
93+
LLM_MAX_TOKENS: ${LLM_MAX_TOKENS:-2000}
94+
LLM_TIMEOUT: ${LLM_TIMEOUT:-300}
95+
TZ: Asia/Singapore
96+
volumes:
97+
- config_data:/app/config
98+
depends_on:
99+
postgres:
100+
condition: service_healthy
101+
minio:
102+
condition: service_healthy
103+
networks:
104+
- tracepcap-network
105+
106+
# Nginx - Serves frontend and proxies API to backend (pre-built image)
107+
# Note: frontend build args (VITE_*) are baked into this image at build time.
108+
# To change them, rebuild the image with pull-and-save-images.sh.
109+
nginx:
110+
image: tracepcap-nginx:latest
111+
container_name: tracepcap-nginx
112+
environment:
113+
APP_MEMORY_MB: ${APP_MEMORY_MB:-2048}
114+
TZ: Asia/Singapore
115+
ports:
116+
- "${NGINX_PORT:-80}:80"
117+
depends_on:
118+
- backend
119+
networks:
120+
- tracepcap-network
121+
122+
volumes:
123+
postgres_data:
124+
driver: local
125+
minio_data:
126+
driver: local
127+
config_data:
128+
driver: local
129+
130+
networks:
131+
tracepcap-network:
132+
driver: bridge

scripts/load-images.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# load-images.sh
3+
#
4+
# Run this on the OFFLINE machine after copying the images/ folder here.
5+
#
6+
# What it does:
7+
# Loads every .tar file in ./images/ into the local Docker daemon.
8+
#
9+
# Usage:
10+
# bash scripts/load-images.sh
11+
#
12+
# After loading, start the stack with:
13+
# docker compose -f docker-compose.offline.yml up -d
14+
15+
set -euo pipefail
16+
17+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
19+
IMAGES_DIR="$ROOT_DIR/images"
20+
21+
if [ ! -d "$IMAGES_DIR" ]; then
22+
echo "Error: images/ directory not found at $IMAGES_DIR"
23+
echo "Make sure you copied the images/ folder from the internet-connected machine."
24+
exit 1
25+
fi
26+
27+
# Collect .tar files
28+
shopt -s nullglob
29+
TAR_FILES=("$IMAGES_DIR"/*.tar)
30+
shopt -u nullglob
31+
32+
if [ ${#TAR_FILES[@]} -eq 0 ]; then
33+
echo "Error: No .tar files found in $IMAGES_DIR/"
34+
echo "Run pull-and-save-images.sh on an internet-connected machine first."
35+
exit 1
36+
fi
37+
38+
echo "=== Loading Docker images from images/ ==="
39+
echo ""
40+
for tarfile in "${TAR_FILES[@]}"; do
41+
echo " Loading $(basename "$tarfile")..."
42+
docker load -i "$tarfile"
43+
done
44+
45+
echo ""
46+
echo "=== All images loaded successfully ==="
47+
echo ""
48+
echo "Start the application with:"
49+
echo " docker compose -f docker-compose.offline.yml up -d"

scripts/pull-and-save-images.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env bash
2+
# pull-and-save-images.sh
3+
#
4+
# Run this on an internet-connected machine BEFORE transferring to the offline host.
5+
#
6+
# What it does:
7+
# 1. Pulls all third-party images from Docker Hub
8+
# 2. Builds the backend and nginx images locally
9+
# 3. Saves every image as a .tar file under ./images/
10+
#
11+
# Usage:
12+
# bash scripts/pull-and-save-images.sh
13+
#
14+
# Build args for nginx are read from .env (if present) — copy .env.example first
15+
# if you haven't already configured it.
16+
17+
set -euo pipefail
18+
19+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
21+
IMAGES_DIR="$ROOT_DIR/images"
22+
23+
BACKEND_IMAGE="tracepcap-backend:latest"
24+
NGINX_IMAGE="tracepcap-nginx:latest"
25+
26+
# ---------------------------------------------------------------------------
27+
# Helper
28+
# ---------------------------------------------------------------------------
29+
save_image() {
30+
local image="$1"
31+
local filename="$2"
32+
echo " Saving $image -> images/$filename"
33+
docker save "$image" -o "$IMAGES_DIR/$filename"
34+
}
35+
36+
# ---------------------------------------------------------------------------
37+
# Load build-arg overrides from .env when available
38+
# ---------------------------------------------------------------------------
39+
if [ -f "$ROOT_DIR/.env" ]; then
40+
echo "Loading build args from .env"
41+
set -a
42+
# shellcheck source=/dev/null
43+
source "$ROOT_DIR/.env"
44+
set +a
45+
fi
46+
47+
mkdir -p "$IMAGES_DIR"
48+
49+
# ---------------------------------------------------------------------------
50+
# 1. Pull third-party images
51+
# ---------------------------------------------------------------------------
52+
echo ""
53+
echo "=== [1/3] Pulling third-party images ==="
54+
55+
# --- Docker Hub ---
56+
DOCKERHUB_IMAGES=(
57+
"postgres:15-alpine"
58+
"minio/minio:latest"
59+
"minio/mc:latest"
60+
)
61+
62+
for img in "${DOCKERHUB_IMAGES[@]}"; do
63+
echo " Pulling $img (Docker Hub)..."
64+
docker pull "$img"
65+
done
66+
67+
# ---------------------------------------------------------------------------
68+
# 2. Build local images
69+
# ---------------------------------------------------------------------------
70+
echo ""
71+
echo "=== [2/3] Building local images ==="
72+
cd "$ROOT_DIR"
73+
74+
echo " Building backend..."
75+
docker build \
76+
-t "$BACKEND_IMAGE" \
77+
./backend
78+
79+
echo " Building nginx (frontend)..."
80+
docker build \
81+
--build-arg VITE_API_BASE_URL=/api \
82+
--build-arg "VITE_SUPPORTED_FILE_TYPES=${VITE_SUPPORTED_FILE_TYPES:-.pcap,.pcapng,.cap}" \
83+
--build-arg "VITE_ANALYSIS_OPTIONS=${VITE_ANALYSIS_OPTIONS:-false}" \
84+
--build-arg "VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT=${VITE_NETWORK_DIAGRAM_CONVERSATION_LIMIT:-false}" \
85+
-t "$NGINX_IMAGE" \
86+
-f ./nginx/Dockerfile \
87+
.
88+
89+
# ---------------------------------------------------------------------------
90+
# 3. Save all images as tars
91+
# ---------------------------------------------------------------------------
92+
echo ""
93+
echo "=== [3/3] Saving images to images/ ==="
94+
95+
save_image "postgres:15-alpine" "postgres_15-alpine.tar"
96+
save_image "minio/minio:latest" "minio_minio.tar"
97+
save_image "minio/mc:latest" "minio_mc.tar"
98+
save_image "$BACKEND_IMAGE" "tracepcap-backend.tar"
99+
save_image "$NGINX_IMAGE" "tracepcap-nginx.tar"
100+
101+
# ---------------------------------------------------------------------------
102+
# Summary
103+
# ---------------------------------------------------------------------------
104+
echo ""
105+
echo "=== Done ==="
106+
echo ""
107+
echo "Transfer the following to the offline machine:"
108+
echo " images/ (all .tar files)"
109+
echo " docker-compose.offline.yml"
110+
echo " .env (or .env.example — configure before starting)"
111+
echo " scripts/load-images.sh"
112+
echo ""
113+
echo "Then on the offline machine run:"
114+
echo " bash scripts/load-images.sh"
115+
echo " docker compose -f docker-compose.offline.yml up -d"

0 commit comments

Comments
 (0)