---
title: "Deploy Windows Server"
description: "Windows Server desktop with optional network pause for Railway Serverless."
category: "Other"
url: https://railway.com/deploy/windows-server
---

# Deploy Windows Server

Windows Server desktop with optional network pause for Railway Serverless.

**[Deploy Windows Server on Railway](https://railway.com/template/windows-server)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/windows-server/manifest.json

- **Creator:** Xav's Projects
- **Category:** Other
- **Total deploys:** 1

## Template content

### Windows Server https://thumb.wikimedia.org/wikipedia/commons/thumb/4/48/Windows_logo_-_2012_%28dark_blue%29.svg/3840px-Windows_logo_-_2012_%28dark_blue%29.svg.png?utm_source=fr.wikipedia.org&utm_campaign=index&utm_content=thumbnail

- **Image:** dockurr/windows@sha256:59f8bed19cfd49cf1701debe34f9df6311e61a4e2ae68aa9f41206e2184e31cc
- **Start command:** `python3 -c 'import os;from pathlib import Path;defaults={'"'"'VERSION'"'"': '"'"'2022'"'"', '"'"'RAM_SIZE'"'"': '"'"'4G'"'"', '"'"'CPU_CORES'"'"': '"'"'4'"'"', '"'"'CPU_MODEL'"'"': '"'"'Nehalem'"'"', '"'"'CPU_FLAGS'"'"': '"'"'-vmx,-avx,-avx2,-fma,-aes,-pclmulqdq'"'"', '"'"'VM'"'"': '"'"'N'"'"', '"'"'VMX'"'"': '"'"'N'"'"', '"'"'DISK_SIZE'"'"': '"'"'48G'"'"', '"'"'DISK_FMT'"'"': '"'"'qcow2'"'"', '"'"'KVM'"'"': '"'"'N'"'"', '"'"'HV'"'"': '"'"'N'"'"', '"'"'RAM_CHECK'"'"': '"'"'N'"'"', '"'"'ADAPTER'"'"': '"'"'e1000e'"'"', '"'"'NETWORK'"'"': '"'"'slirp'"'"', '"'"'IP'"'"': '"'"'10.0.2.15'"'"', '"'"'DNSMASQ_DISABLE'"'"': '"'"'Y'"'"', '"'"'SAMBA'"'"': '"'"'N'"'"', '"'"'PROTECT'"'"': '"'"'Y'"'"', '"'"'USERNAME'"'"': '"'"'admin'"'"', '"'"'WEB_PORT'"'"': '"'"'8006'"'"', '"'"'MANUAL'"'"': '"'"'N'"'"', '"'"'QMP'"'"': '"'"'unix:/storage/windows-server/qmp.sock,server=on,wait=off'"'"', '"'"'SERVERLESS_PAUSE_NETWORK'"'"': '"'"'false'"'"', '"'"'SERVERLESS_IDLE_SECONDS'"'"': '"'"'300'"'"'};[os.environ.setdefault(k,v) for k,v in defaults.items()];[Path(d).mkdir(parents=True,exist_ok=True) for d in ("/windows-server","/oem")];files={'"'"'/windows-server/launch.sh'"'"': '"'"'#!/usr/bin/env bash\nset -Eeuo pipefail\n\nPASSWORD="${PASSWORD:-}"\nif [[ ${#PASSWORD} -lt 16 ]]; then\n  printf \'"'"'Windows Server requires PASSWORD with at least 16 characters.\\n\'"'"' >&2\n  exit 1\nfi\nexport PASS="$PASSWORD" PROTECT=Y\nmkdir -p /storage/windows-server\nchmod 700 /storage/windows-server\n\npython3 /windows-server/controller.py &\n# Preserve Dockur\'"'"'s ACPI shutdown handling. Railway\'"'"'s drainingSeconds must be\n# 120 so that Windows can flush its disk before the container is replaced.\nexec /run/entry.sh\n'"'"', '"'"'/windows-server/controller.py'"'"': '"'"'"""Local installation confirmation. No public diagnostic interface."""\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom pathlib import Path\nimport json\nimport time\nimport uuid\nimport threading\nimport idle_network\n\nROOT = Path(\'"'"'/storage/windows-server\'"'"')\nROOT.mkdir(parents=True, exist_ok=True)\nINSTANCE = str(uuid.uuid4())\nREADY = threading.Event()\nif Path(\'"'"'/storage/windows.boot\'"'"').exists():\n    READY.set()\n\ndef record(data):\n    row = {\'"'"'host_epoch\'"'"': time.time(), \'"'"'instance\'"'"': INSTANCE, **data}\n    with (ROOT / \'"'"'events.jsonl\'"'"').open(\'"'"'a\'"'"') as f:\n        f.write(json.dumps(row) + \'"'"'\\n\'"'"')\n    print(\'"'"'[windows-server] \'"'"' + json.dumps(row), flush=True)\n\nclass Handler(BaseHTTPRequestHandler):\n    def do_POST(self):\n        try:\n            size = int(self.headers.get(\'"'"'Content-Length\'"'"', 0))\n            if not 0 < size < 16384 or self.path != \'"'"'/report\'"'"':\n                raise ValueError(\'"'"'Invalid report\'"'"')\n            data = json.loads(self.rfile.read(size))\n            if data.get(\'"'"'stage\'"'"') not in (\'"'"'setup-complete\'"'"', \'"'"'boot-ready\'"'"', \'"'"'persistence-check\'"'"', \'"'"'network-check\'"'"'):\n                raise ValueError(\'"'"'Unknown report\'"'"')\n            record({\'"'"'event\'"'"': \'"'"'guest\'"'"', \'"'"'report\'"'"': data})\n            if data[\'"'"'stage\'"'"'] == \'"'"'setup-complete\'"'"':\n                disk = Path(\'"'"'/storage/data.qcow2\'"'"')\n                if disk.exists() and disk.stat().st_size > 1048576:\n                    Path(\'"'"'/storage/windows.boot\'"'"').touch()\n                    READY.set()\n            self.send_response(204)\n            self.end_headers()\n        except Exception:\n            self.send_error(400)\n\n    def do_GET(self):\n        if self.path != \'"'"'/setup.ps1\'"'"':\n            return self.send_error(404)\n        body = Path(\'"'"'/oem/setup.ps1\'"'"').read_bytes()\n        self.send_response(200)\n        self.send_header(\'"'"'Content-Length\'"'"', str(len(body)))\n        self.end_headers()\n        self.wfile.write(body)\n\n    def log_message(self, *_):\n        pass\n\nrecord({\'"'"'event\'"'"': \'"'"'container-start\'"'"'})\nidle_network.start(READY, record)\nHTTPServer((\'"'"'127.0.0.1\'"'"', 8877), Handler).serve_forever()\n'"'"', '"'"'/windows-server/idle_network.py'"'"': '"'"'"""Opt-in guest NIC pause based on noVNC connections; QMP stays local."""\nimport json\nimport os\nfrom pathlib import Path\nimport socket\nimport threading\nimport time\n\nQMP_SOCKET = \'"'"'/storage/windows-server/qmp.sock\'"'"'\nBOOT_GRACE_SECONDS = 180\nPOLL_SECONDS = 5\n\n\nclass QMP:\n    def __enter__(self):\n        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        self.sock.settimeout(3)\n        try:\n            self.sock.connect(QMP_SOCKET)\n            self.stream = self.sock.makefile(\'"'"'rwb\'"'"')\n            if \'"'"'QMP\'"'"' not in self.read():\n                raise RuntimeError(\'"'"'Missing QMP greeting\'"'"')\n            self.command(\'"'"'qmp_capabilities\'"'"')\n            return self\n        except Exception:\n            self.__exit__(None, None, None)\n            raise\n\n    def read(self):\n        line = self.stream.readline(1048576)\n        if not line:\n            raise ConnectionError(\'"'"'QMP disconnected\'"'"')\n        return json.loads(line)\n\n    def command(self, name, arguments=None):\n        request = {\'"'"'execute\'"'"': name}\n        if arguments is not None:\n            request[\'"'"'arguments\'"'"'] = arguments\n        self.stream.write(json.dumps(request).encode() + b\'"'"'\\n\'"'"')\n        self.stream.flush()\n        while True:\n            response = self.read()\n            if \'"'"'error\'"'"' in response:\n                raise RuntimeError(\'"'"'QMP command failed: \'"'"' + name)\n            if \'"'"'return\'"'"' in response:\n                return response[\'"'"'return\'"'"']\n\n    def clients(self):\n        state = self.command(\'"'"'query-vnc\'"'"')\n        if state.get(\'"'"'enabled\'"'"') is not True:\n            raise RuntimeError(\'"'"'VNC state unavailable\'"'"')\n        return len(state.get(\'"'"'clients\'"'"', []))\n\n    def link(self, up):\n        self.command(\'"'"'set_link\'"'"', {\'"'"'name\'"'"': \'"'"'net0\'"'"', \'"'"'up\'"'"': up})\n\n    def __exit__(self, *_):\n        if hasattr(self, \'"'"'stream\'"'"'):\n            self.stream.close()\n        self.sock.close()\n\n\nclass IdlePolicy:\n    def __init__(self, timeout, now):\n        self.timeout = timeout\n        self.started = now\n        self.last_active = now\n        self.was_ready = False\n        self.paused = False\n\n    def action(self, now, ready, clients):\n        if not ready:\n            self.last_active = now\n            self.was_ready = False\n            return \'"'"'resume\'"'"' if self.paused else None\n        if not self.was_ready:\n            self.last_active = now\n            self.was_ready = True\n        if clients:\n            self.last_active = now\n            return \'"'"'resume\'"'"' if self.paused else None\n        if now - self.started < BOOT_GRACE_SECONDS:\n            return None\n        if not self.paused and now - self.last_active >= self.timeout:\n            return \'"'"'pause\'"'"'\n        return None\n\n\ndef settings(environ):\n    enabled = environ.get(\'"'"'SERVERLESS_PAUSE_NETWORK\'"'"', \'"'"'false\'"'"').strip().lower()\n    if enabled not in (\'"'"'true\'"'"', \'"'"'false\'"'"'):\n        raise ValueError(\'"'"'SERVERLESS_PAUSE_NETWORK must be true or false\'"'"')\n    if enabled == \'"'"'false\'"'"':\n        return None\n    timeout = int(environ.get(\'"'"'SERVERLESS_IDLE_SECONDS\'"'"', \'"'"'300\'"'"'))\n    if not 30 <= timeout <= 86400:\n        raise ValueError(\'"'"'SERVERLESS_IDLE_SECONDS must be between 30 and 86400\'"'"')\n    return timeout\n\n\ndef step(policy, ready, qmp, now, record):\n    action = policy.action(now, ready, qmp.clients())\n    if action == \'"'"'pause\'"'"':\n        # Check again immediately before cutting the NIC to narrow the race\n        # with a new desktop connection. The next poll also restores it.\n        if qmp.clients():\n            policy.last_active = now\n            return\n        qmp.link(False)\n        policy.paused = True\n        record({\'"'"'event\'"'"': \'"'"'network-paused\'"'"', \'"'"'idle_seconds\'"'"': round(now - policy.last_active)})\n    elif action == \'"'"'resume\'"'"':\n        qmp.link(True)\n        policy.paused = False\n        record({\'"'"'event\'"'"': \'"'"'network-resumed\'"'"', \'"'"'reason\'"'"': \'"'"'desktop-connected\'"'"'})\n\n\ndef monitor(timeout, ready, record):\n    policy = IdlePolicy(timeout, time.monotonic())\n    last_error = -float(\'"'"'inf\'"'"')\n    while not Path(\'"'"'/run/shm/qemu.end\'"'"').exists():\n        now = time.monotonic()\n        try:\n            with QMP() as qmp:\n                step(policy, ready.is_set(), qmp, now, record)\n        except Exception as error:\n            # Uncertain desktop state must not trigger a cut. If already\n            # paused, restore connectivity whenever the monitor is reachable.\n            if policy.paused:\n                try:\n                    with QMP() as qmp:\n                        qmp.link(True)\n                    policy.paused = False\n                    record({\'"'"'event\'"'"': \'"'"'network-resumed\'"'"', \'"'"'reason\'"'"': \'"'"'monitor-recovery\'"'"'})\n                except Exception:\n                    pass\n            policy.last_active = now\n            if now - last_error >= 60:\n                record({\'"'"'event\'"'"': \'"'"'network-monitor-wait\'"'"', \'"'"'error\'"'"': type(error).__name__})\n                last_error = now\n        time.sleep(POLL_SECONDS)\n\n\ndef start(ready, record):\n    try:\n        timeout = settings(os.environ)\n    except ValueError as error:\n        record({\'"'"'event\'"'"': \'"'"'network-pause-disabled\'"'"', \'"'"'reason\'"'"': str(error)})\n        return\n    if timeout is not None:\n        record({\'"'"'event\'"'"': \'"'"'network-monitor-start\'"'"', \'"'"'idle_seconds\'"'"': timeout,\n                \'"'"'boot_grace_seconds\'"'"': BOOT_GRACE_SECONDS})\n        threading.Thread(target=monitor, args=(timeout, ready, record), daemon=True).start()\n'"'"', '"'"'/oem/install.bat'"'"': '"'"'@echo off\npowershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\\OEM\\setup.ps1 > C:\\OEM\\setup.log 2>&1\n'"'"', '"'"'/oem/setup.ps1'"'"': '"'"'$ErrorActionPreference = \'"'"'Stop\'"'"'\n$Root = \'"'"'C:\\ProgramData\\RailwayWindowsServer\'"'"'\nNew-Item -ItemType Directory -Path $Root -Force | Out-Null\n\n# Windows receives a DHCP address from QEMU. Use explicit external resolvers\n# because Dockur\'"'"'s container resolver is disabled for Railway\'"'"'s unprivileged\n# user-mode network.\nGet-NetAdapter | Where-Object Status -eq \'"'"'Up\'"'"' | ForEach-Object {\n    Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex -ServerAddresses @(\'"'"'1.1.1.1\'"'"', \'"'"'1.0.0.1\'"'"')\n}\n\n$BootScript = @\'"'"'\n$ErrorActionPreference = \'"'"'Continue\'"'"'\n$Root = \'"'"'C:\\ProgramData\\RailwayWindowsServer\'"'"'\n$Identity = (Get-ItemProperty \'"'"'HKLM:\\SOFTWARE\\Microsoft\\Cryptography\'"'"').MachineGuid\n$Proof = Join-Path $Root \'"'"'persistence-proof.txt\'"'"'\nif (-not (Test-Path $Proof)) { [Guid]::NewGuid().ToString() | Set-Content $Proof }\n$OS = Get-CimInstance Win32_OperatingSystem\n$Data = @{ stage = \'"'"'boot-ready\'"'"'; machine = $Identity; proof = (Get-Content $Proof -Raw).Trim(); caption = $OS.Caption; boot = $OS.LastBootUpTime.ToString(\'"'"'o\'"'"') }\ntry {\n    $WC = New-Object Net.WebClient\n    $WC.Proxy = $null\n    $WC.Headers[\'"'"'Content-Type\'"'"'] = \'"'"'application/json\'"'"'\n    [void]$WC.UploadString(\'"'"'http://10.0.2.1:8877/report\'"'"', \'"'"'POST\'"'"', ($Data | ConvertTo-Json -Compress))\n    $WC.Dispose()\n} catch { $_ | Out-File (Join-Path $Root \'"'"'boot-report-error.txt\'"'"') }\n\'"'"'@\n$BootPath = Join-Path $Root \'"'"'boot.ps1\'"'"'\n$BootScript | Set-Content -Path $BootPath -Encoding UTF8\n$Action = New-ScheduledTaskAction -Execute \'"'"'powershell.exe\'"'"' -Argument "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$BootPath`""\n$Trigger = New-ScheduledTaskTrigger -AtStartup\nRegister-ScheduledTask -TaskName \'"'"'RailwayWindowsServerBoot\'"'"' -Action $Action -Trigger $Trigger -User \'"'"'SYSTEM\'"'"' -RunLevel Highest -Force | Out-Null\n\n$WC = New-Object Net.WebClient\n$WC.Proxy = $null\n$WC.Headers[\'"'"'Content-Type\'"'"'] = \'"'"'application/json\'"'"'\n[void]$WC.UploadString(\'"'"'http://10.0.2.1:8877/report\'"'"', \'"'"'POST\'"'"', \'"'"'{"stage":"setup-complete"}\'"'"')\n$WC.Dispose()\n& $BootPath\n'"'"'};[Path(d).write_text(s) for d,s in files.items()];os.execv("/usr/bin/tini",["tini","-s","/bin/bash","/windows-server/launch.sh"])'`
- **Public domain:** Yes

## Documentation

# Deploy and Host Windows Server on Railway

Run **Windows Server** on Railway with a browser-accessible desktop, PowerShell and a persistent Windows system disk. This template packages Dockur Windows with authenticated HTTPS access, automatic installation and graceful shutdown. The default edition is Windows Server 2022 with Desktop Experience, downloaded directly from Microsoft during first startup. Optional environment variables can pause the Windows network after the browser desktop disconnects to help Railway Serverless sleep; see the Serverless section before enabling them.

## About Hosting Windows Server

Windows runs in a QEMU virtual machine inside one Railway service. Open the generated HTTPS domain to access the desktop through noVNC. The persistent `/storage` volume contains the Windows system disk, installed applications, files and settings. No external database is required.

Railway does not provide usable KVM acceleration in the tested environment, so this template uses CPU emulation. Booting Windows and launching applications are slower than on a hardware-accelerated Windows VPS. Allow at least 8 GB of service memory and 4 vCPU; Windows receives 4 GB RAM and four virtual CPU cores by default.

## First connection

1. Deploy the template with a volume of at least 64 GB mounted at `/storage`. Confirm the allocated volume size before installation; Railway defaults and limits depend on your plan.
2. Open the generated HTTPS domain. Use `USERNAME` (default `admin`) and the generated `PASSWORD` from Railway Variables.
3. Wait for the automatic Windows installation to finish. The initial installation can take around 30 minutes or longer; the web interface is available while installation runs.
4. Use the same account and password for the Windows session if prompted.

The default password is unique to each deployment. All public desktop assets and WebSocket connections require authentication. The template exposes only HTTP port `8006` through Railway HTTPS. Public RDP and VNC ports are not configured.

## Common Use Cases

- A Windows Server desktop accessible from a browser.
- Windows administration with PowerShell and graphical management tools.
- Running compatible Windows applications and server tools.
- Maintaining a persistent Windows environment across container restarts and redeployments.

## Dependencies for Windows Server Hosting

- One service using the pinned Dockur Windows image and the startup script included in the template.
- One persistent volume at `/storage`, at least 64 GB for the default 48 GB virtual disk and installation media.
- A paid Railway plan allowing at least 8 GB memory, 4 vCPU and a 64 GB persistent volume for this service.
- Access to Microsoft's download servers during installation.
- Appropriate Microsoft usage rights for the selected edition and hosting environment.

### Deployment Dependencies

- [Dockur Windows image and upstream source](https://github.com/dockur/windows)
- [Microsoft Windows Server 2022 installation media](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022)
- [Railway persistent volumes](https://docs.railway.com/volumes/reference#size-limits)
- [Railway Serverless](https://docs.railway.com/deployments/serverless), when enabled after installation

The template Start Command contains the complete initialization wrapper. It writes the launcher and Windows setup files, then starts Dockur.

## Why Deploy Windows Server on Railway?

- Browser desktop through one authenticated HTTPS domain.
- Persistent Windows system disk, including installed software and user files.
- Generated credentials instead of a shared default password.
- Automatic installation from Microsoft's official media.
- A pinned Dockur image for repeatable deployments.
- Graceful ACPI shutdown with a 120-second Railway draining period.
- Optional automatic guest network pause to help Railway Serverless sleep.

## Configuration

| Variable | Default | Purpose |
| --- | --- | --- |
| `USERNAME` | `admin` | Initial Windows account and web login. |
| `PASSWORD` | Generated | Initial Windows password and web password; minimum 16 characters. |
| `VERSION` | `2022` | Windows Server 2022 Evaluation installation media. |
| `RAM_SIZE` | `4G` | Memory assigned to Windows. Keep headroom in the service limit. |
| `CPU_CORES` | `4` | Emulated CPU cores. |
| `ADAPTER` | `e1000e` | Network adapter validated for complete HTTP transfers on Railway. |
| `DISK_SIZE` | `48G` | Windows virtual disk capacity. Keep the Railway volume larger. |
| `SERVERLESS_PAUSE_NETWORK` | `false` | Set `true` to pause the Windows network after the last browser desktop disconnects. |
| `SERVERLESS_IDLE_SECONDS` | `300` | Idle delay before network pause, in seconds (30–86400). |

Account variables configure the initial Windows installation. Changing them after installation does not update the existing Windows account; manage that account inside Windows. The web password follows Railway's `PASSWORD` variable.

Keep one replica attached to the volume. Save work before stopping, redeploying or enabling Serverless. A persistent disk preserves saved data, not running applications or unsaved documents. Keep the 120-second draining period so Windows can shut down cleanly.

## Serverless and automatic network pause

Both Railway Serverless and automatic network pause are **off by default**. Windows services can send background traffic and prevent Railway from sleeping, even when the desktop tab is closed. The optional network pause disconnects the Windows virtual network adapter when no browser desktop is connected, allowing Railway to detect inactivity.

### Enable after installation

1. Finish the first Windows installation with Railway Serverless disabled. Enabling Serverless during installation can interrupt setup.
2. In Railway Variables set `SERVERLESS_PAUSE_NETWORK=true`. Keep `SERVERLESS_IDLE_SECONDS=300` for a five-minute delay, or choose another value between 30 and 86400 seconds.
3. Enable **Serverless** in the Railway service settings and redeploy to apply both changes. The environment variable alone does not enable Railway Serverless.
4. Save your work and close every noVNC desktop tab. An open connected desktop tab counts as activity even if you are not typing.

### What happens

- While a browser desktop is connected, Windows keeps its network access.
- After the last desktop disconnects and the idle delay expires, the wrapper disconnects the entire Windows virtual NIC. A three-minute minimum boot grace period protects startup, and the wrapper waits for the initial installation to complete before allowing a network pause.
- The authenticated web desktop remains reachable. Reopening a desktop connection restores the NIC at the next check (normally within five seconds), plus Windows network reconnection time.
- If Railway has already put the service to sleep, opening its HTTPS URL starts the container and boots Windows with the NIC enabled. Saved files remain on `/storage`; running applications and unsaved work are not preserved.
- Railway normally sleeps 5–10 minutes after the last outbound packet, according to its [Serverless documentation](https://docs.railway.com/deployments/serverless). With a 300-second network pause delay, expect roughly 10–15 minutes after closing the desktop, sometimes longer. Network pause helps remove Windows background traffic; it does not force Railway's sleep timer.

**The idle check measures browser desktop connections, not Windows workload activity.** When enabled, it also interrupts downloads, updates, background jobs, outbound connections and any server application using the Windows NIC. RDP sessions and other Windows clients do not keep this mode awake. Keep `SERVERLESS_PAUSE_NETWORK=false` when those workloads require continuous networking. Set it back to `false` and redeploy to disable the automatic pause; disable Railway Serverless too if the server must remain running.

Storage remains billed while compute sleeps. Keep the 120-second draining period for a clean Windows shutdown.

### Validation

On 2026-11-08, with 4 vCPU / 8 GB service memory and 4 GB guest RAM, the automatic mode was tested with a **60-second** idle delay. Networking stayed enabled throughout a connected desktop session and paused about one minute after disconnection. A reconnection restored Internet without rebooting Windows; HTTPS returned a complete response. After another disconnection, Railway reached `SLEEPING` in **6 min 08 s**, about five minutes after the NIC pause. An HTTPS request received the authentication response in **1.19 s** and the Windows desktop was observed after **1 min 41 s**, with the saved file preserved and HTTPS working again.

These are measured test results, not guaranteed timers. The default five-minute pause delay adds four minutes compared with the one-minute test setting. With normal Windows networking and the pause option disabled, the earlier test stayed awake for more than 15 minutes.

## Windows licensing

The default Microsoft download is **Windows Server 2022 Evaluation**, with a 180-day evaluation period and Microsoft's evaluation restrictions. No Windows activation license or product key is sold or supplied by this template.

For ongoing or production use, obtain the appropriate Windows Server licensing and third-party hosting rights for your deployment. Activation alone does not establish those rights. Sleep does not extend the evaluation period. Review [Microsoft's evaluation information](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022) and [outsourcing licensing guidance](https://www.microsoft.com/licensing/guidance/Outsourcing).

Community-maintained packaging, not affiliated with Microsoft, Dockur or Railway. Windows remains subject to Microsoft's terms; the wrapper code follows this repository's MIT license.


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/windows-server
