Setting Up a ServiceNow MID Server in 3 Minutes (No Docker Hub Required)
Spinning Up a ServiceNow MID Server Without Docker Hub
The moers/mid-server image on Docker Hub is convenient, but what if you want to understand what’s actually happening under the hood? Or what if you need to run a MID server on a machine without internet access to Docker Hub?
Turns out, ServiceNow publishes version-specific MID server container recipes directly from your instance. You just need to ask it what version it’s running.
The Secret: mid.version
Every ServiceNow instance stores its own MID server version as a system property:
GET /api/now/table/sys_properties?sysparm_query=name=mid.versionThis returns something like:
mid-linux-container-recipe.2024-12-19_1707272914Or on newer instances:
australia-02-11-2026__patch2-04-17-2026_04-29-2026_2044Either way, the last MM-DD-YYYY pattern in that string is the build date, which tells you exactly where to download the matching container recipe from install.service-now.com.
The Script
Here’s a standalone shell script that does the whole thing — query your instance, download the recipe, build the Docker image, and start the MID server. This is adapted from John Dahl’s servicenow_mid_server_docker_setup_script with a few tweaks for broader version format support:
#!/usr/bin/env bash
set -euo pipefail
# Configuration — set these here or in a separate secrets.sh
mid_display_name="${mid_display_name:-aiinabox-mid}"
mid_server_name="${mid_server_name:-docker_mid_server}"
servicenow_instance="${servicenow_instance:-}"
mid_username="${mid_username:-}"
mid_password="${mid_password:-}"
# Load secrets from external file (if present)
if [[ -f ./secrets.sh ]]; then
source ./secrets.sh
fi
# Validate required vars
if [[ -z "$servicenow_instance" ]]; then
echo "ERROR: servicenow_instance is not set."
exit 1
fi
if [[ -z "$mid_username" || -z "$mid_password" ]]; then
echo "ERROR: mid_username and mid_password must be set."
exit 1
fi
# Check prerequisites
for cmd in curl unzip docker; do
if ! command -v "$cmd" &>/dev/null; then
echo "ERROR: '$cmd' not found."
exit 1
fi
done
if ! docker info &>/dev/null; then
echo "ERROR: Cannot talk to Docker. Is it running and are you in the docker group?"
exit 1
fi
# Stop existing container
if [[ -f docker-compose.yaml ]]; then
echo "Shutting down existing container..."
docker compose down 2>/dev/null || true
fi
# Get MID version from instance
echo "Querying $servicenow_instance for MID version..."
url="https://${servicenow_instance}.service-now.com/api/now/table/sys_properties?sysparm_query=name=mid.version&sysparm_fields=value&sysparm_limit=1"
response=$(curl -s "$url" --request GET \
--header "Accept:application/json" \
--user "${mid_username}:${mid_password}")
if echo "$response" | grep -q '"error"'; then
echo "ERROR: API call failed. Check your credentials."
echo "$response"
exit 1
fi
# Extract version (no jq needed — use cut)
releasename=$(echo "$response" | cut -d'"' -f6)
echo "MID Server release: $releasename"
# Extract build date — finds the last MM-DD-YYYY in the string
# Handles both:
# mid-linux-container-recipe.2024-12-19_1707272914
# australia-02-11-2026__patch2-04-17-2026_04-29-2026_2044
build_date=$(echo "$releasename" | grep -oE '[0-9]{2}-[0-9]{2}-[0-9]{4}' | tail -1)
rel_month=$(echo "$build_date" | cut -d'-' -f1)
rel_day=$(echo "$build_date" | cut -d'-' -f2)
rel_year=$(echo "$build_date" | cut -d'-' -f3)
# Download the recipe
filename="mid-linux-container-recipe.${releasename}.linux.x86-64.zip"
recipe_url="https://install.service-now.com/glide/distribution/builds/package/app-signed/mid-linux-container-recipe/${rel_year}/${rel_month}/${rel_day}/${filename}"
echo "Downloading $filename ..."
curl -fSL "$recipe_url" -o "$filename"
mkdir -p ./recipe ./export
chmod 777 ./export
unzip -o "$filename" -d ./recipe
mid_server_version=$(echo "$filename" | cut -d'.' -f2)
# Build the Docker image
echo "Building image ${mid_server_name}:${mid_server_version} ..."
docker build --tag "${mid_server_name}:${mid_server_version}" ./recipe
# Clean up
rm -r ./recipe
rm -f "$filename"
# Create docker-compose.yaml and start
cat > docker-compose.yaml <<EOF
services:
${mid_server_name}:
container_name: ${mid_server_name}
image: ${mid_server_name}:${mid_server_version}
restart: unless-stopped
volumes:
- ./export:/opt/snc_mid_server/agent/export
environment:
MID_INSTANCE_URL: "https://${servicenow_instance}.service-now.com/"
MID_INSTANCE_USERNAME: "${mid_username}"
MID_INSTANCE_PASSWORD: "${mid_password}"
MID_SERVER_NAME: "${mid_display_name}"
EOF
echo "Starting MID server..."
docker compose up -d
echo "Done! Validate at: https://$servicenow_instance.service-now.com/mid_server_list.do"How It Works
- Query your instance for the
mid.versionproperty - Extract the build date — the last
MM-DD-YYYYpattern in the version string, works for any format - Download the official recipe from
install.service-now.comfor that exact build - Build a Docker image from the recipe
- Start it with Docker Compose, mounting the export directory for file attachments
The beauty of this approach: you’re getting the exact MID server build that matches your instance, directly from ServiceNow’s distribution servers. No third-party Docker images, no version guesswork.
Why This Matters
- Air-gapped environments — download the recipe once, bake it into your base image
- Auditability — you control exactly what gets built and run
- No Docker Hub dependency — everything comes from your instance or
install.service-now.com - Version-locked — your MID server always matches your instance version
Usage
Create a secrets.sh file alongside the script:
servicenow_instance="dev383416"
mid_username="mid_user"
mid_password="your_password"Then run:
chmod +x midserver.sh
./midserver.shThe script will query your instance, download the matching recipe, build the Docker image, and start the MID server. Validate it in ServiceNow under MID Servers and you’re done.