I've previously written about my home lab setup; just to recap, though, I have a tiny PC that runs Home Assistant on a Hyper-V VM, and I was running Pi-hole, unbound, and WireGuard as Docker containers. I've since dumped most of that since I got a UniFi Dream 7 and switched to AdGuard, but that's a post for another day.
The wife would sometimes complain that some of her apps would stop working, and I found that restarting Pi-hole or unbound would resolve it. Then the issue would come back, and I'd have to restart it again. Thanks to Portainer, I was able to add a button to Home Assistant that would call the API and restart either container, which made it accessible for her to do without me. Great!
I created a PowerShell script that ran as a scheduled task to restart the containers every night that seemed to help for a bit.
Unfortunately, that wasn't always the fix. I would log in to the PC and find that Docker just randomly crashed and didn't bother to restart itself. Thank you for that. After a restart of Docker, things would work again for a bit. UNTIL the next time when I would find that WSL had crashed, which would also crash Docker. Note: The issues are most likely due to the low powered tiny PC and that it runs 24/7.
Finally, I had enough and created THE script, and that is what I have to share with you.
You can find the complete script on my GitHub as usual, but I'll walk through some of it. There is some configuration at the top that you can adjust however you need. This script is also for my Windows machine. The general concepts should be enough to help you should you use Linux.
I have a dynamic DNS through Namecheap that lets me point my VPN client to a specific subdomain that should always point to whatever my home IP address is. Thankfully they have an API for this. Their "beta" client is terrible, so this is my homegrown solution. It's a simple HTTP request to their update endpoint with my home IP address.
# This updates a dynamic dns record of a Namecheap domain.
function Update-DDNS {
Write-Host "Checking DDNS"
# Define variables
$domain = "[your domain].com"
$record = "[subdomain]"
$ddnsPassword = "[your password]"
$updateUrl = "https://dynamicdns.park-your-domain.com/update?host=$record&domain=$domain&password=$ddnsPassword"
# Optional: Get the current public IP (not necessary for Namecheap, but useful for logging)
$publicIp = Invoke-RestMethod -Uri "https://dynamicdns.park-your-domain.com/getip"
# Make the DDNS update request
$response = Invoke-RestMethod -Uri $updateUrl -Method Get
# Output the result
Write-Host "Public IP: $publicIp"
Write-Host "Namecheap Response:"
$response
}
The next set of functions is all about checking to see if Docker is running, and if not, restart it ( Test-DockerRunning
, Restart-Docker
). You may or may not be using the Docker backend process. You can check that by using Get-Process *docker*
If it's not running, you may want to remove those lines from this function.
# Function to check if Docker is running
function Test-DockerRunning {
Write-Host "Checking WSL"
if ((Get-WslDistribution docker-desktop | Select-Object -First 1).State -ne "Running") {
return false
}
Write-Host "Checking Docker"
$dockerProcess = Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue
return $dockerProcess -ne $null
}
# Function to restart Docker
function Restart-Docker {
Write-Log "WARN: Killing Docker"
Stop-Process -Name "Docker Desktop" -Force
Stop-Process -Name "com.docker.backend" -Force
Write-Log "WARN: Restarting Docker..."
Start-Process -FilePath "C:\Program Files\Docker\Docker\resources\com.docker.backend.exe"
Start-Process -FilePath "C:\Program Files\Docker\Docker\Docker Desktop.exe"
Start-Sleep -Seconds 20 # Give Docker time to start
}
Similarly, there are functions to check to see if a given container is running ( Test-ContainerRunning
, Restart-Container
).
# Function to restart a container
function Restart-Container($containerName) {
Write-Log "WARN: Restarting container $containerName..."
docker restart $containerName
Start-Sleep -Seconds $retryDelay # Wait for the container to restart
}
# Function to check if a container is running
function Test-ContainerRunning($containerName) {
$container = docker ps --format "{{.Names}}" | Select-String -Pattern $containerName
return $container -ne $null
}
The next function Test-URLAccessible
does a simple web request and checks for a 200 status code. This allows me to make sure that the containers with web portals are available. In some cases, I noticed the container was running but had log errors or something and wasn't actually running.
# Function to check if the service URL is accessible
function Test-URLAccessible($testUrl) {
try {
$response = Invoke-WebRequest -Uri $testUrl -UseBasicParsing -TimeoutSec 5
return $response.StatusCode -eq 200
} catch {
return $false
}
}
Test-DNSContainer
is for containers that provide some kind of DNS lookup services like unbound, pihole, or adguard. This function attempts to resolve a domain name to verify these services are running correctly.
# Function to check if a DNS container is resolving queries
function Test-DNSContainer($queryDomain) {
try {
$dnsResult = Resolve-DnsName -Name $queryDomain -ErrorAction SilentlyContinue
return $dnsResult -ne $null
} catch {
return $false
}
}
As I mentioned previously, I have Home Assistant running on a Hyper-V VM. Test-HomeAssistantAccessible
checks that the portal URL is running. Obviously, Restart-HomeAssistantVM
will restart that VM.
# Function to check if Home Assistant is accessible
function Test-HomeAssistantAccessible {
return Test-URLAccessible $homeAssistantUrl
}
# Function to restart Home Assistant VM
function Restart-HomeAssistantVM {
Write-Log "WARN: Restarting Home Assistant VM..."
Stop-VM -Name $homeAssistantVM -Force
Start-VM -Name $homeAssistantVM
Start-Sleep -Seconds $retryDelay # Wait for VM to boot
}
Write-Log
prints to both the console and to a log file. Since I run this as a scheduled task, I wanted the ability to see what checks failed and when things are getting restarted.
Trim-Log
makes sure that the log file doesn't get too big and cuts the topmost part of the log to a defined length.
function Write-Log {
param(
[string]$Message
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp`t$Message"
Add-Content -Path $LogPath -Value $logEntry
Write-Output $logEntry
}
function Trim-Log {
if (Test-Path $LogPath) {
$lines = Get-Content $LogPath
if ($lines.Count -gt $MaxLogLines) {
$trimmed = $lines[-$MaxLogLines..-1]
$trimmed | Set-Content $LogPath
}
}
}
The remaining part of the script loops over any defined containers and utilizes these functions to make sure everything is working.
I hope that some of these scripts are helpful to you. As always, drop a comment below if you have questions, and I'll do my best to answer.
Comments
You can also comment directly on GitHub.