IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Docker Compose Rollback on Windows Server 2025

By Raman Kumar

Share:

Updated on Oct 25, 2026

Docker Compose Rollback on Windows Server 2025

Use case and rollout plan

This tutorial shows you how to run a production Docker Compose deployment on Windows Server 2025 and roll it back safely if a release breaks login, database connectivity, or health checks. The goal is not just to start containers. You will set up a simple release directory structure, deploy one compose stack, test it, swap to a new version, and then roll back cleanly with logs and verification.

If you are placing a customer-facing app on Hostperl infrastructure, this is the kind of procedure that keeps a bad release from becoming a support ticket. A Windows Server instance from Hostperl VPS is a practical fit when your workload needs Windows-specific tooling, PowerShell automation, or IIS-adjacent operations alongside containers.

This guide uses Windows Server 2025 because the task is platform-specific. Docker Desktop is not the right choice for a production Windows Server host. Instead, you will use Docker Engine on Windows Server with PowerShell, Windows Firewall, and Compose files stored under a controlled release path.

What you will build

You will create two application release directories under C:\app:

  • C:\app\releases\v1 for the current release
  • C:\app\releases\v2 for the candidate release
  • C:\app\current as the active symlink-style junction to the live release

The app itself is a small Nginx container serving a static health page. That keeps the tutorial focused on rollback mechanics rather than application code. The same pattern works for web apps, worker stacks, or APIs once you swap in your own image and ports.

Connect to the server and identify Windows Server

On your local computer

ssh root@203.0.113.10

203.0.113.10 is a documentation example. Replace it with the real public IP assigned to your Windows Server instance. If your provider gave you a different initial admin account, use that account in the first SSH command instead.

On the VPS as the initial administrator in PowerShell

Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer

This confirms you are on Windows Server 2025 or a compatible Windows Server build. You should see the product name and version details in the output.

Prepare a non-root administrator and folder layout

Windows Server does not use Linux sudo groups, but you should still work as a non-admin operator once the server is set up. On a fresh server, create a local administrator account for deployment tasks, then keep the original elevated session open until the new login is verified.

On the VPS as root-equivalent administrator in PowerShell

$Password = Read-Host -AsSecureString
New-LocalUser -Name "deploy" -Password $Password -FullName "Deployment Admin" -Description "Compose deployment account"
Add-LocalGroupMember -Group "Administrators" -Member "deploy"
New-Item -ItemType Directory -Path C:\app\releases -Force | Out-Null
New-Item -ItemType Directory -Path C:\app\shared -Force | Out-Null

This creates a local account named deploy, adds it to the Administrators group, and prepares release directories. Use your own account name if needed, but keep the deployment folder structure intact.

Open a second terminal and sign in as deploy. If that works, continue. Do not remove the original admin session yet.

Install Docker Engine and Compose support

Windows Server 2025 supports Docker Engine for Windows containers and Linux containers through the supported Windows Server path you choose. For this tutorial, we will use a Linux container image on a Windows host only if your environment already supports the required container runtime. If your host is configured for Windows containers only, use the same rollback pattern with a Windows container image instead.

On the VPS as the administrator in PowerShell

Install-WindowsFeature -Name Containers -IncludeAllSubFeature -IncludeManagementTools
Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart

After the reboot, reopen PowerShell as administrator.

On the VPS as the administrator in PowerShell

Invoke-WebRequest -UseBasicParsing -Uri https://download.docker.com/win/static/stable/x86_64/docker-27.2.0.zip -OutFile C:\Windows\Temp\docker.zip
Expand-Archive C:\Windows\Temp\docker.zip -DestinationPath C:\Program Files\Docker
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Docker", [EnvironmentVariableTarget]::Machine)
Start-Service docker

These commands place Docker binaries in C:\Program Files\Docker, add them to the system path, and start the service. If you already have a supported Docker runtime installed through your image or management process, do not reinstall it blindly.

On the VPS as the administrator in PowerShell

docker version
Get-Service docker

You should see client and server version output, and the Docker service should report Running.

Open the firewall only for the app port you need

This example uses port 8080 for the application. Add the rule before you start testing remote access.

On the VPS as the administrator in PowerShell

New-NetFirewallRule -DisplayName "Compose App 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow

This creates an inbound rule for TCP 8080. If your app will be published on a different port, change the port here and in the Compose file below.

Create the first release

Next, create the initial release files. The app is only a static Nginx container, but the same structure works for a real service when you replace the image and mount points.

On the VPS as the deploy user in PowerShell

New-Item -ItemType Directory -Path C:\app\releases\v1 -Force | Out-Null
Set-Content -Path C:\app\releases\v1\docker-compose.yml -Value @'
services:
  web:
    image: nginx:1.27-alpine
    container_name: compose-web
    ports:
      - "8080:80"
    volumes:
      - C:\app\releases\v1\html:C:\usr\share\nginx\html:ro
    restart: unless-stopped
'@
New-Item -ItemType Directory -Path C:\app\releases\v1\html -Force | Out-Null
Set-Content -Path C:\app\releases\v1\html\index.html -Value @'


    

Release v1

Compose rollback baseline is healthy.

'@

This creates a Compose file and a simple web page for the first release. The volume path syntax here is Windows-specific. If your container image expects Linux-style mount paths, adjust the image and path mapping to match your runtime.

Start the live release and confirm it works

On the VPS as the deploy user in PowerShell

docker compose -f C:\app\releases\v1\docker-compose.yml up -d
docker ps
Invoke-WebRequest http://127.0.0.1:8080 | Select-Object -ExpandProperty Content

The container should appear in docker ps, and the web request should return the Release v1 page. If the content does not appear, check whether the image pulled correctly and whether port 8080 is already in use.

For supportable app operations, Hostperl publishes guidance on log handling and reload behavior in Node.js logging and reloads with Nginx on Hostperl VPS and Docker restart policies for safer app deployments. The stack type differs, but the operational discipline is the same: keep logs readable, and keep restarts predictable.

Prepare the candidate release

Now build a second version that intentionally changes the visible output. In a real deployment, this would be a new image tag, a new environment file, or a new application build.

On the VPS as the deploy user in PowerShell

New-Item -ItemType Directory -Path C:\app\releases\v2 -Force | Out-Null
Set-Content -Path C:\app\releases\v2\docker-compose.yml -Value @'
services:
  web:
    image: nginx:1.27-alpine
    container_name: compose-web
    ports:
      - "8080:80"
    volumes:
      - C:\app\releases\v2\html:C:\usr\share\nginx\html:ro
    restart: unless-stopped
'@
New-Item -ItemType Directory -Path C:\app\releases\v2\html -Force | Out-Null
Set-Content -Path C:\app\releases\v2\html\index.html -Value @'


    

Release v2

This release is the rollback candidate.

'@

Keep the old release untouched. That gives you a real rollback point instead of a half-edited live directory.

Test the candidate without cutting over yet

Run the new release on a temporary port first. This avoids replacing the live service before you know the new release behaves correctly.

On the VPS as the deploy user in PowerShell

(Get-Content C:\app\releases\v2\docker-compose.yml) -replace '8080:80', '8081:80' | Set-Content C:\app\releases\v2\docker-compose.yml
docker compose -f C:\app\releases\v2\docker-compose.yml up -d
Invoke-WebRequest http://127.0.0.1:8081 | Select-Object -ExpandProperty Content

You should see the Release v2 page on port 8081. If this fails, do not switch production traffic. Fix the image, the file paths, or the port mapping first.

If your deployment uses a reverse proxy in front of Compose, pair this approach with the access-log checks described in Nginx access logs for supportable app hosting. The same discipline helps you spot upstream errors before users do.

Cut over from v1 to v2

Once the candidate works on the test port, switch the live Compose file over to the new release directory. This is the moment to keep the old session open and avoid destructive cleanup.

On the VPS as the deploy user in PowerShell

docker compose -f C:\app\releases\v1\docker-compose.yml down
Copy-Item C:\app\releases\v2\docker-compose.yml C:\app\current\docker-compose.yml -Force
Copy-Item C:\app\releases\v2\html -Destination C:\app\current\html -Recurse -Force
Set-Content -Path C:\app\current\docker-compose.yml -Value @'
services:
  web:
    image: nginx:1.27-alpine
    container_name: compose-web
    ports:
      - "8080:80"
    volumes:
      - C:\app\current\html:C:\usr\share\nginx\html:ro
    restart: unless-stopped
'@
docker compose -f C:\app\current\docker-compose.yml up -d

This stops the old stack, promotes the new files into the active path, and brings the live service back on port 8080. If you keep a symbolic junction at C:\app\current in your own environment, point the Compose file there instead of copying files manually.

Rollback to the last known good release

Rollback should be one command sequence, not an improvised recovery. If the live version misbehaves, revert to the previous release directory and restart the old stack.

On the VPS as the deploy user in PowerShell

docker compose -f C:\app\current\docker-compose.yml down
Set-Content -Path C:\app\current\docker-compose.yml -Value @'
services:
  web:
    image: nginx:1.27-alpine
    container_name: compose-web
    ports:
      - "8080:80"
    volumes:
      - C:\app\releases\v1\html:C:\usr\share\nginx\html:ro
    restart: unless-stopped
'@
docker compose -f C:\app\current\docker-compose.yml up -d
Invoke-WebRequest http://127.0.0.1:8080 | Select-Object -ExpandProperty Content

The output should return to Release v1. If the rollback still fails, check whether the old release files were modified, deleted, or locked by another process.

Verify service health, startup state, and logs

Do not stop at a page load. Confirm that the service starts cleanly and survives a restart.

On the VPS as the deploy user in PowerShell

docker ps
docker logs compose-web --tail 50
Restart-Service docker
Start-Sleep -Seconds 10
docker ps
Invoke-WebRequest http://127.0.0.1:8080 | Select-Object -ExpandProperty StatusCode

The container should return after the Docker service restart, and the status code should be 200. If the container does not come back, your restart policy or service state needs attention before you promote the release.

Failure diagnostics and recovery

Problem: the container will not start. Run:

docker logs compose-web --tail 100

Look for image pull errors, path errors, or port binding conflicts. If the port is busy, confirm it with Get-NetTCPConnection -LocalPort 8080 and stop the conflicting process before retrying.

Problem: remote requests time out. Run:

Get-NetFirewallRule -DisplayName "Compose App 8080"
Test-NetConnection 127.0.0.1 -Port 8080

If the firewall rule is missing, recreate it with New-NetFirewallRule. If localhost works but remote access fails, check the network path between the host and the client.

Problem: the new release works on 8081 but not after cutover. Run:

Get-Content C:\app\current\docker-compose.yml
Invoke-WebRequest http://127.0.0.1:8080 | Select-Object -ExpandProperty Content

If the live Compose file still references the test path or test port, correct it and redeploy. This is the most common mistake during manual promotion.

If you want a Windows-ready environment for controlled deployments, Hostperl can supply the VPS capacity needed for repeatable release rollouts and rollback drills. A properly sized Hostperl VPS gives you room for staging, logs, and backout testing without crowding the production host.

For workloads that need more CPU headroom, dedicated networking, or tighter operational control, consider planning the same deployment flow on dedicated server hosting.

FAQ

Can I use this rollback method for a real app?

Yes. Replace the Nginx image with your app image, keep separate release directories, and test each new build on a non-production port before cutover.

Should I edit the live files directly?

No. Keep each release immutable once tested. That makes rollback predictable and avoids partial edits during an outage.

What if my stack needs a database?

Keep the database outside the application release directory, back it up separately, and test the app against the database before switching traffic. If the database changes are not backward-compatible, plan the rollback path before release day.

How do I know the rollback is safe?

You know it is safe when the old release still starts, returns the expected page or API response, and survives a Docker service restart without manual repair.

Where can I read more about hosting operations around deployments?

Hostperl also covers operational planning in 2026 Hosting Buyer Decision Framework for Real Buyers and Windows-specific recovery patterns in PostgreSQL failover on Windows Server 2025.