Skip to content

Instantly share code, notes, and snippets.

@DurtyFree
Last active October 8, 2025 22:08
Show Gist options
  • Save DurtyFree/5e01cd112811b5e8554074627be7cb4e to your computer and use it in GitHub Desktop.
Save DurtyFree/5e01cd112811b5e8554074627be7cb4e to your computer and use it in GitHub Desktop.
Azure Meilisearch Infrastructure Deployment Script
param(
[string]$SubscriptionId = "<SUBSCRIPTION-ID>",
[string]$ResourceGroup = "rg-meilisearch",
[string]$BaseName = "meilisearch-prod",
[string]$Location = "westeurope",
[string]$AdminUser = "azureuser",
[string]$VmSize = "Standard_D4as_v5",
[int] $MeiliPort = 7700,
[string]$MeiliVersion = "v1.22.3",
[string]$SshAllowedCidr = "<YOUR-IP>/16"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Ensure-Az {
if (-not (az account show 1>$null 2>$null)) { az login | Out-Null }
az account set --subscription $SubscriptionId | Out-Null
}
function Ensure-Rule {
param(
[string]$Rg, [string]$Nsg, [string]$Name,
[int]$Priority, [string]$Dir, [string]$Access, [string]$Proto,
[string]$Src, [string]$DstPorts
)
$exists = az network nsg rule show -g $Rg --nsg-name $Nsg -n $Name 2>$null
if ($exists) {
az network nsg rule update -g $Rg --nsg-name $Nsg -n $Name `
--priority $Priority --direction $Dir --access $Access --protocol $Proto `
--source-address-prefixes $Src --destination-port-ranges $DstPorts 1>$null
}
else {
az network nsg rule create -g $Rg --nsg-name $Nsg -n $Name `
--priority $Priority --direction $Dir --access $Access --protocol $Proto `
--source-address-prefixes $Src --destination-port-ranges $DstPorts 1>$null
}
}
Ensure-Az
# Names
$Vnet = "vnet-$BaseName"
$SubnetApps = "snet-apps"
$Nsg = "nsg-$BaseName-apps"
$VmName = "vm-$BaseName-01"
$NicName = "nic-$VmName"
$PublicIp = "pip-$BaseName-app"
$DnsLabel = ("meili-" + [DateTimeOffset]::UtcNow.ToUnixTimeSeconds())
Write-Host "Ensuring resource group $ResourceGroup"
if (-not (az group exists -n $ResourceGroup | ConvertFrom-Json)) {
az group create -n $ResourceGroup -l $Location 1>$null
}
Write-Host "Ensuring VNet and subnet"
if (-not (az network vnet show -g $ResourceGroup -n $Vnet 2>$null)) {
az network vnet create -g $ResourceGroup -n $Vnet --address-prefixes 10.50.0.0/16 `
--subnet-name $SubnetApps --subnet-prefixes 10.50.1.0/24 1>$null
}
Write-Host "Ensuring NSG and rules"
if (-not (az network nsg show -g $ResourceGroup -n $Nsg 2>$null)) {
az network nsg create -g $ResourceGroup -n $Nsg 1>$null
}
Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "allow-ssh-from-cidr" `
-Priority 101 -Dir Inbound -Access Allow -Proto Tcp -Src $SshAllowedCidr -DstPorts "22"
Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "allow-meili" `
-Priority 110 -Dir Inbound -Access Allow -Proto Tcp -Src "Internet" -DstPorts "$MeiliPort"
Ensure-Rule -Rg $ResourceGroup -Nsg $Nsg -Name "deny-all" `
-Priority 4096 -Dir Inbound -Access Deny -Proto "*" -Src "*" -DstPorts "*"
# Attach NSG to subnet if not attached
$appsSubnetNsg = az network vnet subnet show -g $ResourceGroup --vnet-name $Vnet -n $SubnetApps --query "networkSecurityGroup.id" -o tsv
if ([string]::IsNullOrEmpty($appsSubnetNsg)) {
az network vnet subnet update -g $ResourceGroup --vnet-name $Vnet -n $SubnetApps `
--network-security-group $Nsg 1>$null
}
Write-Host "Ensuring NIC and VM"
if (-not (az network nic show -g $ResourceGroup -n $NicName 2>$null)) {
az network nic create -g $ResourceGroup -n $NicName --vnet-name $Vnet --subnet $SubnetApps 1>$null
}
if (-not (az vm show -g $ResourceGroup -n $VmName 2>$null)) {
az vm create -g $ResourceGroup -n $VmName --image "Ubuntu2204" --size $VmSize `
--nics $NicName --admin-username $AdminUser --generate-ssh-keys 1>$null
}
Write-Host "Attach Standard public IP with DNS to NIC"
if (-not (az network public-ip show -g $ResourceGroup -n $PublicIp 2>$null)) {
az network public-ip create -g $ResourceGroup -n $PublicIp --sku Standard --allocation-method Static 1>$null
}
az network public-ip update -g $ResourceGroup -n $PublicIp --dns-name $DnsLabel 1>$null
$PipId = az network public-ip show -g $ResourceGroup -n $PublicIp --query id -o tsv
$IpCfg = az network nic show -g $ResourceGroup -n $NicName --query "ipConfigurations[0].name" -o tsv
az network nic ip-config update -g $ResourceGroup --nic-name $NicName -n $IpCfg --public-ip-address $PipId 1>$null
$PublicFqdn = az network public-ip show -g $ResourceGroup -n $PublicIp --query "dnsSettings.fqdn" -o tsv
Write-Host "Provision Meilisearch on the VM"
$installScript = @'
set -e
apt-get update
apt-get install -y curl ca-certificates
curl -fL -o /usr/local/bin/meilisearch \
https://github.com/meilisearch/meilisearch/releases/download/__MEILI_VERSION__/meilisearch-linux-amd64
chmod +x /usr/local/bin/meilisearch
/usr/local/bin/meilisearch --version
id meili >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin meili
install -d -o meili -g meili -m 0755 /var/lib/meilisearch
install -d -o meili -g meili -m 0755 /var/lib/meilisearch/data.ms
install -d -o meili -g meili -m 0755 /var/lib/meilisearch/dumps
install -d -o meili -g meili -m 0755 /var/lib/meilisearch/snapshots
KEY="$(grep -E "^MEILI_MASTER_KEY=" /etc/meili.env 2>/dev/null | cut -d= -f2)"
if [ -z "$KEY" ] || [ "$KEY" = "null" ]; then
KEY="$(tr -dc "A-Za-z0-9_.-" </dev/urandom | head -c 40)"
fi
cat >/etc/meili.env <<EOF
MEILI_ENV=production
MEILI_MASTER_KEY=$KEY
MEILI_DB_PATH=/var/lib/meilisearch/data.ms
MEILI_HTTP_ADDR=0.0.0.0:__MEILI_PORT__
EOF
chown root:root /etc/meili.env
chmod 600 /etc/meili.env
cat >/etc/systemd/system/meilisearch.service <<'EOF'
[Unit]
Description=Meilisearch
After=network-online.target
Wants=network-online.target
[Service]
User=meili
Group=meili
Type=simple
WorkingDirectory=/var/lib/meilisearch
EnvironmentFile=/etc/meili.env
ExecStart=/usr/local/bin/meilisearch \
--db-path /var/lib/meilisearch/data.ms \
--http-addr 0.0.0.0:__MEILI_PORT__ \
--env production \
--dump-dir /var/lib/meilisearch/dumps \
--snapshot-dir /var/lib/meilisearch/snapshots
Restart=always
RestartSec=2
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now meilisearch
ufw status | grep -iq active && ufw allow __MEILI_PORT__/tcp || true
sleep 2
STATUS="$(systemctl is-active meilisearch || true)"
HEALTH="$(curl -sS -i --max-time 5 http://127.0.0.1:__MEILI_PORT__/health 2>/dev/null | head -n 1 || echo none)"
MS_KEY="$(grep '^MEILI_MASTER_KEY=' /etc/meili.env | cut -d= -f2)"
echo "MS_STATUS=$STATUS"
echo "MS_HEALTH=$HEALTH"
echo "MS_KEY=$MS_KEY"
'@
$installScript = $installScript.Replace("__MEILI_VERSION__", $MeiliVersion).Replace("__MEILI_PORT__", "$MeiliPort")
$vmOut = az vm run-command invoke -g $ResourceGroup -n $VmName --command-id RunShellScript `
--scripts "$installScript" --query "value[0].message" -o tsv
# Parse simple markers
$msStatus = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_STATUS=" }) -replace "^MS_STATUS=", ""
$msHealth = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_HEALTH=" }) -replace "^MS_HEALTH=", ""
$msKey = ($vmOut -split "`n" | Where-Object { $_ -match "^MS_KEY=" }) -replace "^MS_KEY=", ""
# External health
Start-Sleep -Seconds 3
$extOk = $false
try {
$r = Invoke-WebRequest -Uri ("http://{0}:{1}/health" -f $PublicFqdn, $MeiliPort) -UseBasicParsing -TimeoutSec 8
if ($r.StatusCode -eq 200) { $extOk = $true }
}
catch { }
Write-Host ""
Write-Host "Public endpoint: http://$PublicFqdn:$MeiliPort" -ForegroundColor Cyan
Write-Host "SSH: ssh $AdminUser@$PublicFqdn" -ForegroundColor DarkCyan
Write-Host ("Master key: {0}" -f $(if ($msKey) { $msKey } else { "<unknown>" })) -ForegroundColor Yellow
Write-Host ("Local service: {0}" -f $(if ($msStatus) { $msStatus } else { "<unknown>" })) -ForegroundColor Yellow
Write-Host ("Local health: {0}" -f $(if ($msHealth) { $msHealth } else { "<unknown>" })) -ForegroundColor Yellow
Write-Host ("External health: {0}" -f $(if ($extOk) { "OK" } else { "unreachable" })) -ForegroundColor Yellow
Write-Host ""
Write-Host "Try:" -ForegroundColor Gray
Write-Host " curl -H 'Authorization: Bearer $msKey' http://$PublicFqdn:$MeiliPort/health" -ForegroundColor Gray
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment