I worked this project as an employee at Webnevisan, in a front-end / DevOps-assistant role. Webnevisan was the company contracted to build and maintain the platform at aramspace.com — Aram Space itself is owned and run by the client company, not by Webnevisan or by me; I was part of the team delivering it, not the project's owner.
The challenge: the task came with no choice in the matter — it had to run on Azure servers. Getting the built app onto Azure and running it under a process supervisor kept breaking in ways that traced back to Azure quietly forcing its own Node.js version onto the app.
A quick word on the app itself
The backend is PHP, and I wasn't involved in it — I only worked the Next.js frontend. But that frontend doesn't call the PHP backend directly from the browser: every backend call is routed through a Next.js API route acting as a proxy. Pages lean on standard Next.js server-side rendering with ISR, and a dedicated /api/revalidate route — guarded by a secret header token — lets the client purge specific paths, or the entire site, the moment something needs to go live immediately instead of waiting out the ISR window. The result reads as a fully dynamic site even though most of it is served from cache.
The problem: Azure's Node version wasn't a suggestion
Deploying the built app straight onto an Azure server and running it under a supervisor kept hitting the same wall: Azure's own Node runtime was pinned to whatever version it wanted to provide, and when that didn't match what the app was built and tested against, small but real bugs showed up — behavior that depended on which Node version actually executed the code, not on anything in the code itself. There was no clean way to just put the app on the server and trust it would run the way it did locally.
The fix: stop shipping code, start shipping an image
Docker's core promise — the runtime ships with the app, instead of being whatever the host happens to provide — was exactly the mismatch this needed to close. Instead of handing Azure raw source and hoping its Node lined up, we wrote a Dockerfile that pins the Node version inside the image itself: a multi-stage build — one stage installs dependencies, one runs next build, and a slim final stage runs only the built output (output: "standalone") on the exact Node version the app needs. Azure's own Node version stopped being able to touch it, because the container brought its own.
Getting the image onto Azure
The workflow: build the image locally, docker login to an Azure Container Registry (authenticated through the Azure CLI), tag the local image with the registry's URL, then docker push it tagged latest. Each new build simply overwrote what latest pointed to in the registry.

The second machine, and not needing to restart it
A separate Azure server was pointed at that registry, running a script instead of a person SSH-ing in to restart a supervisor by hand every time. The script pulls whatever latest currently is and swaps the running container over. Shipping a change stopped meaning "log into a server and restart something" and started meaning "push a new image."
Why this mattered more than the app itself
Building the frontend — Next.js, Bootstrap, and a long list of other libraries for calendars, charts, PDF export, and the rest — was genuinely fun, ordinary front-end work. The deployment problem was the part that actually taught something transferable: it's where Docker stopped being a buzzword read about secondhand and became the tool that fixed a real, reproducible bug in front of a client who required a specific cloud platform, whether or not that platform made it easy.
Where it landed
The app shipped on Azure inside a container that carries its own Node runtime instead of borrowing whatever Azure was handing out. Images build once and push to Azure Container Registry; a second server pulls and swaps them without a manual restart. The client got the Azure deployment they required, and this project became my actual introduction to Docker as an everyday tool rather than something I'd only read about.
What I'd take from this
- A platform mandate like "must run on Azure" constrains where code runs, not what runs there — Docker keeps the actual runtime fixed regardless of what the host tries to hand you.
- Pin the runtime inside the image, not in a README instruction hoping whoever deploys it reads it — a forced Node version is exactly the kind of drift that shows up as a subtle bug, not a hard failure.
- The unglamorous half of a project — the pipeline nobody outside the team ever sees — is sometimes the half that teaches the transferable skill, while the visible half stays valuable but ordinary.