Process Compose has no built-in service install (unlike portless). On Windows, register a Task Scheduler entry.
Use a wrapper script that sets the environment, then have Task Scheduler launch the wrapper. Keeps task definition simple and lets you tweak env without re-registering.
boot-start.ps1 (wrapper)<#
.SYNOPSIS
Boot-time launcher for Process Compose. Sets PATH and launches headless.
#>
[CmdletBinding()]
param()
$ErrorActionPreference = 'Continue'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = (Resolve-Path (Join-Path $scriptDir '..')).Path
$pcExe = Join-Path $root 'bin\process-compose.exe'
$pcYaml = Join-Path $root 'process-compose.yaml'
$logFile = Join-Path $root 'logs\process-compose.log'
$bootLog = Join-Path $root 'logs\boot-start.log'
New-Item -ItemType Directory -Force -Path (Join-Path $root 'logs') | Out-Null
"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] boot-start invoked. User: $env:USERNAME" | Out-File -FilePath $bootLog -Append
# Build PATH explicitly. Tune for your machine.
$pathParts = @(
"$root\bin" # PC + any committed binaries
"C:\Program Files\Git\usr\bin" # openssl, bash, coreutils
"C:\Program Files\Git\bin" # git
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python313" # python, pythonw
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python313\Scripts" # uv, pip, etc.
"C:\Program Files (x86)\cloudflared" # optional: cloudflared
"C:\Windows\System32"
"C:\Windows"
$env:PATH
)
$env:PATH = ($pathParts -join ';')
# Optional: load secrets from gitignored .env (e.g. API keys)
$envFile = Join-Path $root '.env'
if (Test-Path $envFile) {
Get-Content $envFile | ForEach-Object {
if ($_ -match '^\s*([A-Z_]+)\s*=\s*(.+?)\s*$') {
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
}
}
}
# Ensure incompatible env vars are unset (example: OAuth-only services that
# refuse to start with stale API keys)
# [Environment]::SetEnvironmentVariable('SOME_API_KEY', $null, 'Process')
"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] Starting process-compose..." | Out-File -FilePath $bootLog -Append
# -p 8888 API port (pick something free, avoid 8080 if you have other tools there)
# -t=false no TUI (headless daemon mode)
# -L PC's own log file
& $pcExe -p 8888 -t=false -L $logFile up -f $pcYaml
"[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')] process-compose exited code $LASTEXITCODE" | Out-File -FilePath $bootLog -Append
boot-task-install.ps1 (registers the task)[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
# Must be admin to create scheduled tasks
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Run as Administrator."
}
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = (Resolve-Path (Join-Path $scriptDir '..')).Path
$bootScript = Join-Path $scriptDir 'boot-start.ps1'
$taskName = "ProcessCompose-MyStack" # rename per project
# Idempotent: remove existing if present
Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue |
Unregister-ScheduledTask -Confirm:$false
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$bootScript`"" `
-WorkingDirectory $root
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1)
# S4U: run at boot as user without interactive logon or stored password
$taskPrincipal = New-ScheduledTaskPrincipal `
-UserId $env:USERNAME `
-LogonType S4U `
-RunLevel Highest
Register-ScheduledTask -TaskName $taskName `
-Action $action -Trigger $trigger -Settings $settings -Principal $taskPrincipal `
-Description "Starts Process Compose at boot."
| LogonType | Runs at boot before logon? | Needs password? | Capability |
|---|---|---|---|
Interactive |
No — waits for user logon | No | Full user context (UI, network shares) |
S4U |
Yes | No | User context but no UI, no network shares |
Password |
Yes | Yes (stored encrypted) | Full user context |
ServiceAccount |
Yes (as Local System / Network Service) | No | Limited to service account perms — typically can't read user files |
For Process Compose managing user-scoped dev services, S4U is usually the right choice: services run as the user (can read C:\Users\<user>\...) without requiring an interactive logon.
# Check task exists
Get-ScheduledTask -TaskName "ProcessCompose-MyStack" |
Format-List TaskName, State, Triggers, Principal
# Manually run the task to test before reboot
Start-ScheduledTask -TaskName "ProcessCompose-MyStack"
# Wait, then check PC is up
Start-Sleep -Seconds 10
process-compose -p 8888 process list
After a reboot, if services don't come up:
<root>/logs/boot-start.log — confirm the wrapper actually ran<root>/logs/process-compose.log — confirm PC started and look for process-spawn errors.\scripts\boot-start.ps1 and watch what happens.Common failures:
pathParts arrayprocess-compose.yaml are absolute.env file not in expected locationnetstat -ano | findstr :8888portless has its own boot task. The two are independent — register both:
portless service install # registers portless's task
.\scripts\boot-task-install.ps1 # registers PC's task
# Verify both
Get-ScheduledTask | Where-Object {
$_.TaskName -like "*ortless*" -or $_.TaskName -like "*ompose*"
}
# In the same script:
Get-ScheduledTask -TaskName "ProcessCompose-MyStack" -ErrorAction SilentlyContinue |
Unregister-ScheduledTask -Confirm:$false
portless service uninstall