- Install and start Docker Desktop.
- Copy the scripts in this Gist into a folder named
C:\SelfHosted\BentoPDF.Launch-BentoPDF.ps1is the entrypoint to run BentoPDF.- It pulls the latest image of
bentopdf/bentopdf-simplefrom Docker Hub and launches a Docker container from it. - It can automatically update the image if it is older than 7 days and run a fresh Docker container.
- It pulls the latest image of
bentopdf-idle-monitor.ps1is a helper script called byLaunch-BentoPDF.ps1.- It monitors the Docker container running BentoPDF for any network activity such as browser tabs connecting over the container port. Idle Docker container will be stopped after a default timeout of 30 minutes.
- It will automatically exit if the Docker container running BentoPDF is stopped externally by some other method.
- Update Settings to allow executing Powershell scripts. Quick way is to right-click Windows Powershell to Run as administrator and then execute -
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
- Run Windows Powershell as the current user and execute -
cd C:\SelfHosted\BentoPDF .\Launch-BentoPDF.ps1 - BentoPDF will be available in your browser at http://localhost:8181.
- (Optional): Verify that some config files are auto-generated by the scripts in
C:\SelfHosted\BentoPDF. File structure will look something like -C:\SelfHosted\BentoPDF\ ├── Launch-BentoPDF.ps1 ✅ Main launcher ├── bentopdf-idle-monitor.ps1 ✅ Idle monitor ├── bentopdf-config.json ✅ Auto-created └── bentopdf-monitor-config.json ✅ Auto-created - (Optional): Create a Desktop shortcut named
BentoPDFto launch target -
After the Desktop shortcut is created -powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\SelfHosted\BentoPDF\Launch-BentoPDF.ps1"- Right-click on the shortcut to open Properties.
- Set the working directory with Start in: or a similar field pointing to
C:\SelfHosted\BentoPDF. Click Apply and then OK to save the changes. - Open
%AppData%\Microsoft\Windows\Start Menu\Programsfolder in the File Explorer and then Copy-Paste the Desktop shortcut in that folder. This will make the shortcut available as an App in Start.
Last active
January 19, 2026 08:19
-
-
Save rajeakshay/a0f45d14f2195db844e642f4484f4676 to your computer and use it in GitHub Desktop.
Self-Hosting BentoPDF in Windows 11 with Docker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # BentoPDF Idle Monitor | |
| # Monitors container for idle activity and stops it after configured timeout | |
| # Set window title for Task Manager identification | |
| $Host.UI.RawUI.WindowTitle = "BentoPDF Idle Monitor" | |
| $ErrorActionPreference = "SilentlyContinue" | |
| # Get script directory and load config | |
| $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path | |
| $ConfigFile = Join-Path $ScriptDir "bentopdf-monitor-config.json" | |
| # Load configuration | |
| if (-not (Test-Path $ConfigFile)) { | |
| Write-Host "Monitor config not found. Exiting." -ForegroundColor Red | |
| exit 1 | |
| } | |
| try { | |
| $config = Get-Content $ConfigFile -Raw | ConvertFrom-Json | |
| $containerName = $config.ContainerName | |
| $idleMinutes = $config.IdleMinutes | |
| $port = $config.Port | |
| } catch { | |
| Write-Host "Failed to load monitor config. Exiting." -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host "BentoPDF Idle Monitor Started" -ForegroundColor Cyan | |
| Write-Host "Container: $containerName" -ForegroundColor Gray | |
| Write-Host "Port: $port" -ForegroundColor Gray | |
| Write-Host "Idle timeout: $idleMinutes minutes" -ForegroundColor Gray | |
| Write-Host "Monitoring started at $(Get-Date -Format 'HH:mm:ss')" -ForegroundColor Gray | |
| $lastActivityTime = Get-Date | |
| $checkIntervalSeconds = 60 | |
| while ($true) { | |
| Start-Sleep -Seconds $checkIntervalSeconds | |
| # Check if container is still running | |
| $running = docker ps --filter "name=^$containerName`$" --format "{{.Names}}" 2>$null | |
| if ($running -ne $containerName) { | |
| Write-Host "Container stopped externally. Exiting monitor." -ForegroundColor Yellow | |
| exit 0 | |
| } | |
| # Check for active TCP connections on the port | |
| $connections = Get-NetTCPConnection -LocalPort $port -State Established -ErrorAction SilentlyContinue | |
| if ($connections) { | |
| $lastActivityTime = Get-Date | |
| Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Active connections detected" -ForegroundColor Green | |
| } else { | |
| $idleTime = (Get-Date) - $lastActivityTime | |
| $minutesIdle = [Math]::Round($idleTime.TotalMinutes, 1) | |
| Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Idle for $minutesIdle minutes" -ForegroundColor Gray | |
| if ($idleTime.TotalMinutes -ge $idleMinutes) { | |
| Write-Host "`nNo activity for $idleMinutes minutes. Stopping container..." -ForegroundColor Yellow | |
| docker stop $containerName | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Host "Container '$containerName' stopped successfully." -ForegroundColor Green | |
| } else { | |
| Write-Host "Failed to stop container." -ForegroundColor Red | |
| } | |
| exit 0 | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # BentoPDF Docker Launcher Script | |
| param( | |
| [int]$Port = 8181, | |
| [string]$ContainerName = "bentopdf", | |
| [string]$ImageName = "bentopdf/bentopdf-simple:latest", | |
| [int]$MaxDockerWaitSeconds = 60, | |
| [int]$UpdateCheckDays = 7, | |
| [switch]$ForceUpdate, | |
| [int]$IdleShutdownMinutes = 30, | |
| [switch]$NoIdleShutdown | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path | |
| $ConfigFile = Join-Path $ScriptDir "bentopdf-config.json" | |
| $MonitorScript = Join-Path $ScriptDir "bentopdf-idle-monitor.ps1" | |
| function Write-ColorOutput { | |
| param([string]$Message, [string]$Color = "White") | |
| Write-Host $Message -ForegroundColor $Color | |
| } | |
| function Get-Configuration { | |
| if (Test-Path $ConfigFile) { | |
| try { | |
| return (Get-Content $ConfigFile -Raw | ConvertFrom-Json) | |
| } | |
| catch { | |
| return @{LastUpdateCheck = "1970-01-01T00:00:00"; LastImagePull = "1970-01-01T00:00:00"} | |
| } | |
| } | |
| return @{LastUpdateCheck = "1970-01-01T00:00:00"; LastImagePull = "1970-01-01T00:00:00"} | |
| } | |
| function Save-Configuration { | |
| param($Config) | |
| try { | |
| $Config | ConvertTo-Json | Set-Content $ConfigFile -Force | |
| } | |
| catch { | |
| Write-ColorOutput "Warning: Could not save configuration." "Yellow" | |
| } | |
| } | |
| function Test-DockerRunning { | |
| try { | |
| $ErrorActionPreference = "SilentlyContinue" | |
| $output = docker version --format '{{.Server.Version}}' 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| $ErrorActionPreference = "Stop" | |
| if ($exitCode -eq 0 -and $output) { | |
| return $true | |
| } | |
| return $false | |
| } | |
| catch { | |
| return $false | |
| } | |
| } | |
| function Wait-ForDocker { | |
| param([int]$MaxWaitSeconds) | |
| Write-ColorOutput "Waiting for Docker Engine..." "Yellow" | |
| $elapsed = 0 | |
| while ($elapsed -lt $MaxWaitSeconds) { | |
| if (Test-DockerRunning) { | |
| Write-ColorOutput "Docker Engine is ready!" "Green" | |
| return $true | |
| } | |
| Start-Sleep -Seconds 2 | |
| $elapsed += 2 | |
| Write-Host "." -NoNewline | |
| } | |
| Write-Host "" | |
| return $false | |
| } | |
| function Get-LocalImageDate { | |
| param([string]$ImageName) | |
| try { | |
| $imageInfo = docker inspect $ImageName 2>$null | ConvertFrom-Json | |
| if ($imageInfo -and $imageInfo.Created) { | |
| return [DateTime]::Parse($imageInfo.Created) | |
| } | |
| } | |
| catch { | |
| return $null | |
| } | |
| return $null | |
| } | |
| function Update-DockerImage { | |
| param([string]$ImageName, [object]$Config) | |
| Write-ColorOutput "`nChecking for updates..." "Yellow" | |
| $currentDate = Get-LocalImageDate -ImageName $ImageName | |
| Write-ColorOutput "Pulling latest image..." "Yellow" | |
| $null = docker pull $ImageName 2>&1 | |
| $newDate = Get-LocalImageDate -ImageName $ImageName | |
| $Config.LastUpdateCheck = (Get-Date).ToString("o") | |
| if ($currentDate -and $newDate -and ($newDate -gt $currentDate)) { | |
| Write-ColorOutput "New version downloaded!" "Green" | |
| $Config.LastImagePull = (Get-Date).ToString("o") | |
| Save-Configuration -Config $Config | |
| return $true | |
| } | |
| elseif (-not $currentDate -and $newDate) { | |
| Write-ColorOutput "Image downloaded!" "Green" | |
| $Config.LastImagePull = (Get-Date).ToString("o") | |
| Save-Configuration -Config $Config | |
| return $true | |
| } | |
| else { | |
| Write-ColorOutput "Already on latest version." "Green" | |
| Save-Configuration -Config $Config | |
| return $false | |
| } | |
| } | |
| function Test-ContainerExists { | |
| param([string]$Name) | |
| try { | |
| $result = docker ps -a --filter "name=^${Name}$" --format "{{.Names}}" 2>$null | |
| return ($result -eq $Name) | |
| } | |
| catch { | |
| return $false | |
| } | |
| } | |
| function Test-ContainerRunning { | |
| param([string]$Name) | |
| try { | |
| $result = docker ps --filter "name=^${Name}$" --format "{{.Names}}" 2>$null | |
| return ($result -eq $Name) | |
| } | |
| catch { | |
| return $false | |
| } | |
| } | |
| function Get-ContainerPort { | |
| param([string]$Name) | |
| try { | |
| $portInfo = docker port $Name 8080 2>$null | |
| if ($portInfo -match ':(\d+)$') { | |
| return [int]$Matches[1] | |
| } | |
| } | |
| catch { | |
| return $null | |
| } | |
| return $null | |
| } | |
| function Start-IdleMonitor { | |
| param([string]$ContainerName, [int]$IdleMinutes, [int]$Port) | |
| if (-not (Test-Path $MonitorScript)) { | |
| Write-ColorOutput "Warning: Idle monitor script not found" "Yellow" | |
| return | |
| } | |
| $monitorConfig = @{ | |
| ContainerName = $ContainerName | |
| IdleMinutes = $IdleMinutes | |
| Port = $Port | |
| } | |
| $monitorConfigFile = Join-Path $ScriptDir "bentopdf-monitor-config.json" | |
| $monitorConfig | ConvertTo-Json | Set-Content $monitorConfigFile -Force | |
| # Start with a custom window title that will appear in Task Manager | |
| Start-Process powershell.exe -ArgumentList "-WindowStyle Hidden -ExecutionPolicy Bypass -Command `"& {`$Host.UI.RawUI.WindowTitle='BentoPDF Idle Monitor'; & '$MonitorScript'}`"" -WindowStyle Hidden | |
| } | |
| Write-ColorOutput "`n=== BentoPDF Launcher ===" "Cyan" | |
| Write-ColorOutput "Container: $ContainerName" "Cyan" | |
| Write-ColorOutput "Port: $Port" "Cyan" | |
| $config = Get-Configuration | |
| if ($config.LastImagePull -ne "1970-01-01T00:00:00") { | |
| $lastPull = [DateTime]::Parse($config.LastImagePull) | |
| $daysSince = [Math]::Round(((Get-Date) - $lastPull).TotalDays, 1) | |
| Write-ColorOutput "Last update: $daysSince days ago" "Cyan" | |
| } | |
| if (-not $NoIdleShutdown -and $IdleShutdownMinutes -gt 0) { | |
| Write-ColorOutput "Idle shutdown: $IdleShutdownMinutes minutes" "Cyan" | |
| } | |
| Write-ColorOutput "" "White" | |
| # Debug: Test Docker | |
| $dockerTest = Test-DockerRunning | |
| Write-ColorOutput "Docker status check: $dockerTest" "Gray" | |
| if (-not $dockerTest) { | |
| Write-ColorOutput "Starting Docker Desktop..." "Yellow" | |
| $dockerPath = "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe" | |
| if (Test-Path $dockerPath) { | |
| Start-Process $dockerPath | |
| if (-not (Wait-ForDocker -MaxWaitSeconds $MaxDockerWaitSeconds)) { | |
| Write-ColorOutput "Docker did not start in time." "Red" | |
| Read-Host "Press Enter to exit" | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-ColorOutput "Docker Desktop not found." "Red" | |
| Read-Host "Press Enter to exit" | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-ColorOutput "Docker is running." "Green" | |
| } | |
| $needsUpdate = $ForceUpdate -or ((Get-Date) - [DateTime]::Parse($config.LastUpdateCheck)).TotalDays -ge $UpdateCheckDays | |
| if ($needsUpdate) { | |
| $updated = Update-DockerImage -ImageName $ImageName -Config $config | |
| if ($updated -and (Test-ContainerExists -Name $ContainerName)) { | |
| Write-ColorOutput "Recreating container with new version..." "Yellow" | |
| $null = docker stop $ContainerName 2>&1 | |
| $null = docker rm $ContainerName 2>&1 | |
| } | |
| } | |
| else { | |
| $nextCheck = [DateTime]::Parse($config.LastUpdateCheck).AddDays($UpdateCheckDays) | |
| $daysUntil = [Math]::Ceiling(($nextCheck - (Get-Date)).TotalDays) | |
| Write-ColorOutput "Next update check in $daysUntil days" "Gray" | |
| } | |
| $running = Test-ContainerRunning -Name $ContainerName | |
| if ($running) { | |
| Write-ColorOutput "Container already running." "Green" | |
| $actualPort = Get-ContainerPort -Name $ContainerName | |
| if ($actualPort) { | |
| $Port = $actualPort | |
| } | |
| } | |
| else { | |
| if (Test-ContainerExists -Name $ContainerName) { | |
| Write-ColorOutput "Starting existing container..." "Yellow" | |
| $null = docker start $ContainerName | |
| Start-Sleep -Seconds 2 | |
| } | |
| else { | |
| Write-ColorOutput "Creating new container..." "Yellow" | |
| $null = docker run -d --name $ContainerName -p "${Port}:8080" --restart unless-stopped $ImageName | |
| Start-Sleep -Seconds 3 | |
| } | |
| } | |
| if (Test-ContainerRunning -Name $ContainerName) { | |
| $url = "http://localhost:$Port" | |
| Write-ColorOutput "`nBentoPDF is ready!" "Green" | |
| Write-ColorOutput "URL: $url" "Cyan" | |
| if (-not $NoIdleShutdown -and $IdleShutdownMinutes -gt 0) { | |
| Start-IdleMonitor -ContainerName $ContainerName -IdleMinutes $IdleShutdownMinutes -Port $Port | |
| Write-ColorOutput "Idle monitor started ($IdleShutdownMinutes min timeout)" "Cyan" | |
| } | |
| Write-ColorOutput "`nOpening browser..." "Yellow" | |
| try { | |
| Start-Process $url | |
| Write-ColorOutput "Browser opened successfully!" "Green" | |
| Write-ColorOutput "`nBentoPDF is running!" "Green" | |
| Start-Sleep -Seconds 2 | |
| exit 0 | |
| } | |
| catch { | |
| Write-ColorOutput "Failed to open browser. Please navigate to: $url" "Yellow" | |
| Read-Host "Press Enter to exit" | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-ColorOutput "Container failed to start." "Red" | |
| Read-Host "Press Enter to exit" | |
| exit 1 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment