Ritzy One • Deployment Guide
Host the Ritzy One Docker stacks on a Windows Server 2025 machine: Docker Engine inside WSL2 Ubuntu, owned by one dedicated account, reachable from the network, started automatically at boot and backed up every night.
Ritzy One's containers are Linux images. On Windows Server they run inside WSL2 — a real Linux kernel in a lightweight VM that Windows manages — with Docker Engine installed in Ubuntu. Each customer is a separate stack: its own app container, its own Firebird container and its own data volume.
| Choice | Why |
|---|---|
| Docker Engine in WSL2 | Docker Desktop is not supported on Windows Server and needs a paid licence for business use. Windows containers can't run Linux images. |
| Mirrored networking | The containers appear on the server's own IP address, so other PCs reach them directly — no port forwarding. |
| One dedicated account | WSL belongs to one Windows user (next section). |
ritzysvc account| Need | Minimum | Recommended |
|---|---|---|
| Windows | Server 2025 Standard or Datacenter, fully updated | |
| CPU | 4 cores with virtualization | 8 cores |
| RAM | 8 GB | 16 GB or more — each customer stack needs ~1.5–2.5 GB |
| Free disk | 60 GB on the system drive (WSL's disk lives in the user profile) | 200 GB, SSD |
WSL is per Windows user. The Ubuntu distro, Docker and every stack inside it, the WSL settings file and the startup task all belong to the account that created them. Another administrator who signs in sees no Ubuntu at all, and there is no system-wide WSL configuration. So the server gets one account, ritzysvc, that owns everything.
Signed in as any administrator, in PowerShell (Admin). Run the commands one at a time, in order.
First, choose the password. It must:
ritzysvc or “RitzyWeb”;If Windows rejects it, net accounts shows this server's minimum length.
$pw = Read-Host -AsSecureString "Password for ritzysvc"PowerShell shows
Password for ritzysvc: and waits. Now type the password and press Enter; it appears as asterisks.
InvalidPasswordException, because that line contains ritzysvc.New-LocalUser -Name "ritzysvc" -Password $pw -FullName "RitzyWeb Docker host" -Description "Owns WSL, Docker and the RitzyWeb stacks" -PasswordNeverExpires -AccountNeverExpiresIf it fails with
InvalidPasswordException, the password broke a rule above — go back to step 1 and choose another.Add-LocalGroupMember -Group "Administrators" -Member "ritzysvc"
ritzysvc True and an empty PasswordExpires:
Get-LocalUser ritzysvc | Select-Object Name, Enabled, PasswordExpires
New-Item -ItemType Directory -Force C:\RitzyInstall
Now sign out and sign in as ritzysvc. Every remaining step is done in that session.
In PowerShell (Admin), as ritzysvc:
wsl --install -d Ubuntu-24.04
ritzysvc again.ritzy and remember the password — it's needed for sudo.wsl -l -v # Ubuntu-24.04 must show VERSION 2 wsl --version # WSL 2.0 or newer (needed for mirrored networking)If the distro shows version 1:
wsl --set-version Ubuntu-24.04 2. If WSL is older than 2.0: wsl --update.Two files, both needed. Changes to either take effect only after wsl --shutdown.
| File | Where | Controls |
|---|---|---|
.wslconfig | Windows C:\Users\ritzysvc\.wslconfig | The Linux VM: memory, CPUs, networking |
/etc/wsl.conf | Ubuntu inside the distro | systemd, default user, PATH |
.wslconfigCreate it from PowerShell — Notepad on its own tends to save .wslconfig.txt, which WSL ignores:
notepad "$env:USERPROFILE\.wslconfig"
Answer Yes to create the file and paste (values for a 16 GB, 8-core server):
[wsl2] # Memory and CPUs Linux may use. Leave Windows ~4 GB and 1-2 cores. memory=12GB processors=6 # Disk-backed overflow when RAM runs out. swap=4GB # Containers appear on the server's own IP address. Needs WSL 2.0+. networkingMode=mirrored # Resolve DNS through Windows (avoids failures behind corporate DNS or VPNs). dnsTunneling=true # Windows firewall rules, including the Hyper-V rules in section 8, apply to WSL. firewall=true [experimental] # Let WSL's virtual disk shrink when files are deleted. sparseVhd=true
| Server RAM | memory= | processors= | Fits roughly |
|---|---|---|---|
| 8 GB | 5GB | cores − 1 | 1–2 small stacks |
| 16 GB | 12GB | cores − 2 | 3–4 standard stacks |
| 32 GB | 26GB | cores − 2 | 8–10 standard stacks |
/etc/wsl.confIn Ubuntu (wsl -d Ubuntu-24.04): sudo nano /etc/wsl.conf, and make it contain:
[boot] # systemd starts Docker, and cron for the backups, automatically. systemd=true [user] # The Linux account WSL logs in as, including for the startup task. default=ritzy [interop] # Keep the Windows PATH out of Linux. appendWindowsPath=false
Save with Ctrl+O Enter, exit with Ctrl+X, then exit Ubuntu.
# PowerShell: restart WSL, wait ~10 seconds, reopen Ubuntu
wsl --shutdown
wsl -d Ubuntu-24.04
| In Ubuntu, run | Should show |
|---|---|
free -h | total about the memory= you set |
nproc | the processors= number |
ps -p 1 -o comm= | systemd |
ip -4 addr show | grep inet | the server's own LAN IP — proof mirrored networking is on |
whoami | ritzy |
In Ubuntu:
curl -fsSL https://get.docker.com | sudo sh sudo systemctl enable --now docker containerd sudo usermod -aG docker $USER
Leave Ubuntu and open it again so the group change applies, then check:
docker run --rm hello-world # "Hello from Docker!"
Keep the source inside the Linux file system (~/…), not under /mnt/c — builds through the Windows drive are several times slower.
cd ~ && git clone https://github.com/ritzysof/.-ritzy-web-.git ritzyweb cd ~/ritzyweb && git checkout main
No GitHub access on the server? Put a source zip in C:\RitzyInstall and run unzip /mnt/c/RitzyInstall/ritzyweb-source.zip -d ~/ritzyweb.
The image bundles LibreOffice (Word-template PDFs), a Java 11 runtime and the ZATCA fatoora SDK (Saudi e-invoicing). ZATCA only offers its SDK through a captcha-protected download, so it can't be fetched during the build, and the build stops without it.
https://sandbox.zatca.gov.sa/downloadSDK on any PC.C:\RitzyInstall on the server.cp /mnt/c/RitzyInstall/zatca-einvoicing-sdk-*.zip ~/ritzyweb/docker/vendor/
cd ~/ritzyweb && bash install-linux.sh # --port 9090 to move off 8080
The installer skips what's already done, generates the secrets in docker/.env (the PostgreSQL passwords and the sign-in secret), builds the image and starts the stack. The first build takes 10–20 minutes.
cd ~/ritzyweb/docker && docker compose ps # both services "healthy"
On the server, open http://localhost:8080. The login screen offers Set up Ritzy: enter the support password and complete the wizard to create the first company and its administrator account. See the Docker Installation Guide §10 for the wizard itself.
With mirrored networking the app answers on the server's IP. Two firewalls must allow the port: Windows Firewall, and the Hyper-V firewall that guards WSL. In PowerShell (Admin), per port:
New-NetFirewallRule -DisplayName "RitzyWeb Docker 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
New-NetFirewallHyperVRule -Name "RitzyWebDocker8080" -DisplayName "RitzyWeb Docker 8080" -Direction Inbound `
-VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -Protocol TCP -LocalPorts 8080
From another PC, browse to http://<server-ip>:8080.
docker/trial-proxy (Caddy) maps customer.your-domain to each stack's
port and obtains certificates automatically. It needs ports 80 and 443, so they must not be
taken by IIS or an nginx from a native install.
WSL normally starts only when a user opens it. A scheduled task running as ritzysvc starts Ubuntu at boot and keeps it running; Docker then starts with systemd, and every container starts on its own (restart: unless-stopped).
First, on its own, the line that asks for the password. A sign-in box opens; enter the ritzysvc password there (use DOMAIN\ritzysvc for a domain account):
$cred = Get-Credential -UserName "$env:COMPUTERNAME\ritzysvc" -Message "Password of ritzysvc"
Then create the task:
$action = New-ScheduledTaskAction -Execute "C:\Windows\System32\wsl.exe" `
-Argument "-d Ubuntu-24.04 --exec /bin/sleep infinity"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 `
-RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "RitzyWeb WSL (Docker)" -Action $action -Trigger $trigger `
-Settings $settings -User $cred.UserName -Password $cred.GetNetworkCredential().Password -RunLevel Highest
sleep infinity does nothing but keep the Ubuntu session open, so WSL never shuts down.
If the ritzysvc password ever changes, update the task straight away. The first line opens a sign-in box — enter the new password there, then run the second line:
$cred = Get-Credential -UserName "$env:COMPUTERNAME\ritzysvc" -Message "New password of ritzysvc" Set-ScheduledTask -TaskName "RitzyWeb WSL (Docker)" -User $cred.UserName -Password $cred.GetNetworkCredential().Password
ext4.vhdx under C:\Users\ritzysvc\AppData\Local\)
while it runs does not give a consistent database backup.
backup-stacks.sh backs up every stack in one run: a pg_dump of the stack's database (every company in it, consistent while people work), the app's files (company backups, ZATCA certificates, licence files) and the stack's settings. It keeps 14 days locally and exits with an error if anything failed.
# Ubuntu: once sudo apt-get install -y rsync && mkdir -p /mnt/d/RitzyBackups ~/ritzyweb/docker/backup-stacks.sh --rsync /mnt/d/RitzyBackups # test run # schedule it: crontab -e, then add 30 2 * * * $HOME/ritzyweb/docker/backup-stacks.sh --rsync /mnt/d/RitzyBackups >> $HOME/ritzy-backups/backup.log 2>&1
Your Windows backup software can then pick up D:\RitzyBackups. Keep a copy off the server as well, for example with --rclone to a storage box.
Restore steps are in the Docker Installation Guide §13.
Each customer gets its own stack. A customer's extra companies go inside their stack (DBA Console → Add Company), not in a new one.
cd ~/ritzyweb/docker ./new-stack.sh acme 8090 --size standard --start
Then add the two firewall rules from §8 for its port. --size sets the stack's memory and CPU ceilings, so one busy customer can't starve the others:
| Size | For | Memory ceiling (PostgreSQL + app) | CPUs |
|---|---|---|---|
small | 1 company, trials | ~1.4 GB | 1 + 1 |
standard | 2–3 companies | ~2.5 GB | 2 + 2 |
large | 3–5 companies | ~5.5 GB | 4 + 2 |
processors= in .wslconfig fails to
start. Ceilings are limits, not reservations: watch docker stats under real load
before adding more stacks.
An existing web-only customer on a Windows install (companies still in Firebird .FDB files) moves into a stack in three steps: the companies are converted to PostgreSQL on the Windows side, then restored into the stack. Customers who also use the Ritzy 7 desktop app stay on their Windows installation — it needs the Firebird databases.
powershell -ExecutionPolicy Bypass -File docker\migrate\export-windows.ps1 -PgUrl postgres://postgres:<pw>@localhost:5432/postgres
# -InstallDir if not C:\RitzyOne
It converts every company in CompLib.xml with the importer (npm run pg:convert) into a scratch database, dumps it with pg_dump and writes one zip: the dump, the conversion report, a checksum, the settings that must follow the customer (AI key, web-push keys, licensing), the licence files and the ZATCA certificates. The scratch database is dropped afterwards.C:\RitzyInstall on this server. It contains every company's data and private keys — move it only over trusted channels.cd ~/ritzyweb/docker
./new-stack.sh acme 8090 --start # if the stack is new
./migrate/import-to-stack.sh /mnt/c/RitzyInstall/ritzy-migration-<date>.zip ~/ritzy-acme
It verifies the checksum, restores the dump into the stack's database, installs the certificates, licence and settings, and restarts the app. It won't replace a stack that already has companies unless you add --force (the old database is kept as ritzy_pre_import_<date>); companies are not merged.Afterwards: users sign in again (the stack has its own sign-in secret); read the conversion report the import leaves in the stack folder (pg-validation-<date>.md); check that every company opens, that Saudi companies show their ZATCA certificate, and that the licence is valid; run a backup; stop the old Windows service; delete the zip.
ritzysvc (Remote Desktop is fine), then wsl -d Ubuntu-24.04. Other accounts see no Ubuntu.sudo apt-get install -y openssh-server. It listens on the server's IP, port 22 — allow it (both rules, §8) only from your admin network.cd ~/ritzyweb && git pull
cd docker && docker compose -f docker-compose.yml -f docker-compose.build.yml build
# then each stack, when you are ready to move it
cd ~/ritzy-acme && docker compose up -d
Stacks share the image, so the build happens once; each stack moves only when you restart it.
| Symptom | Check |
|---|---|
wsl --install fails, or “virtualization not enabled” | Enable VT-x/AMD-V in the BIOS. Then in PowerShell (Admin): Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All and reboot. |
wsl -l shows no Ubuntu | You're signed in as a different user. Sign in as ritzysvc. |
Memory or CPUs unchanged (free -h, nproc) | .wslconfig isn't in C:\Users\ritzysvc\, or it's named .wslconfig.txt. Fix it, then wsl --shutdown. |
Ubuntu shows only a 172.x.x.x address; other PCs can't connect | Mirrored networking isn't active: wsl --update, check networkingMode=mirrored, wsl --shutdown. On a domain, a Group Policy can block custom networking modes — ask your domain admin. |
| Works on the server, not from other PCs | Both firewall rules in §8, for that port. |
| Port already in use | A Windows program holds it (IIS or a native install's nginx?). Choose another port. |
| Site down after a reboot | Task Scheduler → RitzyWeb WSL (Docker) → Last Run Result. Was the ritzysvc password changed (§9)? |
| Build stops: “put the ZATCA fatoora SDK zip in docker/vendor/” | §6 — copy the Java SDK zip into ~/ritzyweb/docker/vendor/. |
| Build dies with no clear error | Out of memory. Raise memory= to at least 6 GB, then wsl --shutdown. |
| A stack won't start: “range of CPUs” | Its CPU limit is above processors=. Lower PG_CPUS / APP_CPUS in the stack's .env, or use --size small. |
| A container is unhealthy | docker compose logs --tail 100 app (or postgres) in that stack's folder. |
docker: permission denied | Leave and reopen Ubuntu after the usermod in §5. |
docker/INSTALL-WINDOWS-SERVER.md holds this procedure as plain text, and docker/README.md the operational reference.