Introduction
Azure App Service makes it ridiculously easy to host web apps without managing servers. In this hands-on guide, you’ll create a Windows-based App Service, deploy a modern Snake game directly from your browser using Advanced Tools (Kudu), and then practice Deployment Slots with traffic routing and swap, just like a real production rollout.
By the end, your app will be live on a public URL, you’ll have a safe “New Feature” slot for testing, and you’ll understand how to gradually send users to the new version before swapping it into production.
🎯 Skilling Objectives
- Create and configure an Azure App Service (Windows) and Plan.
- Use Advanced Tools (Kudu) to upload/edit web content in
wwwroot
. - Publish a modern HTML/CSS/JS game as your website.
- Create a Deployment Slot, send 6% traffic to it, and swap safely.
Step 1: Create the Web App
💡 What is App Service?
Azure App Service is a PaaS offering for hosting web apps, APIs, and static sites with built-in scaling, deployment slots, and SSL.
Steps
- Log in to the Azure Portal.
Resource Group:
IbrahimRG
(create new if it doesn’t exist).Name:
Console
(or any globally unique name).Publish: Code
Runtime stack: .NET 9 (STS)
Operating System: Windows
App Service Plan: Create new → name it
ConsolePlan
.
✅ Your App Service is live with a default domain.
Step 2: Generate the Game Code
You’ll deploy a single-page app (index.html
) that includes HTML, CSS, and JavaScript for an intuitive, interactive, and modern Snake game: keyboard & touch controls, pause, levels, high score, and a sleek UI.
Copy everything below into a file named
index.html
(you’ll paste it in Step 3).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Snake • Azure Arcade</title>
<style>
:root{
--bg:#0f1222; --panel:#151935; --panel-2:#0b0e1e; --text:#e8ecff; --muted:#9aa3c7;
--accent:#6ee7ff; --accent-2:#a78bfa; --good:#34d399; --bad:#ef4444;
--grid: 24; /* tile size */
}
*{box-sizing:border-box}
html,body{height:100%;margin:0;background:radial-gradient(1200px 700px at 20% -10%, #1b2046 0%, #0f1222 55%) fixed;color:var(--text);font-family:Inter,system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
.wrap{max-width:1100px;margin:0 auto;padding:24px}
header{display:flex;gap:12px;align-items:center;justify-content:space-between;margin-bottom:16px}
.title{font-weight:800;letter-spacing:.3px;font-size:clamp(20px,3.5vw,28px)}
.card{background:linear-gradient(180deg,var(--panel),var(--panel-2));border:1px solid rgba(255,255,255,.06);border-radius:18px;box-shadow:0 10px 30px rgba(0,0,0,.35)}
.board{display:grid;grid-template-columns:1fr 360px;gap:18px}
@media (max-width:960px){.board{grid-template-columns:1fr}}
.canvasWrap{position:relative;padding:16px}
canvas{display:block;margin:0 auto;background:linear-gradient(180deg,#0b1228,#0a0e20);border:1px solid rgba(255,255,255,.06);border-radius:14px}
.hud{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin:12px 0}
.pill{padding:8px 12px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);font-weight:600}
.accent{background:linear-gradient(90deg, rgba(110,231,255,.22), rgba(167,139,250,.22));border-color:rgba(255,255,255,.18)}
.side{padding:18px}
.side h3{margin:6px 0 10px;font-size:14px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em}
.btn{cursor:pointer;user-select:none;display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:12px;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);font-weight:700;transition:transform .05s}
.btn:hover{background:rgba(255,255,255,.08)}
.btn:active{transform:translateY(1px)}
.btn.primary{background:linear-gradient(90deg,#5dd8ff,#9b85ff);color:#051018;border:none}
.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.note{color:var(--muted);font-size:13px;line-height:1.5}
.controls{display:grid;grid-template-columns:repeat(3,64px);gap:8px;justify-content:center;margin-top:10px}
.pad{height:64px}
.pad:nth-child(2){grid-column:2}
.pad:nth-child(3){grid-column:1}
.pad:nth-child(4){grid-column:2}
.pad:nth-child(5){grid-column:3}
.legend{display:flex;gap:10px;justify-content:center;font-size:12px;color:var(--muted);margin-top:8px}
.foodDot,.snakeDot,.wallDot{width:10px;height:10px;border-radius:3px}
.foodDot{background:var(--good)}
.snakeDot{background:var(--accent)}
.wallDot{background:#3b82f6}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="title">Azure Arcade — Advanced Snake</div>
<div class="hud">
<div class="pill accent">Score: <span id="score">0</span></div>
<div class="pill">High: <span id="high">0</span></div>
<div class="pill">Level: <span id="level">1</span></div>
<button id="pauseBtn" class="btn">Pause</button>
<button id="restartBtn" class="btn">Restart</button>
</div>
</header>
<div class="board">
<div class="card canvasWrap">
<canvas id="game" width="720" height="480" aria-label="Snake game area"></canvas>
<div class="legend">
<div class="snakeDot"></div> Snake
<div class="foodDot"></div> Food
<div class="wallDot"></div> Wall
</div>
</div>
<aside class="card side">
<h3>How to play</h3>
<div class="note">Use <b>Arrow keys / WASD</b> to move. Press <b>Space</b> to pause. On mobile, use the on-screen pad.</div>
<div class="controls">
<button class="btn pad" data-dir="up">↑</button>
<button class="btn pad" data-dir="left">←</button>
<button class="btn pad" data-dir="down">↓</button>
<button class="btn pad" data-dir="right">→</button>
</div>
<h3>Game rules</h3>
<ul class="note" style="margin:0 0 10px 18px">
<li>Eat food to grow and score.</li>
<li>Don’t hit walls or yourself.</li>
<li>Speed increases every level (every 5 foods).</li>
</ul>
<div class="grid">
<button id="slowBtn" class="btn">Slow</button>
<button id="fastBtn" class="btn">Fast</button>
<button id="toggleWalls" class="btn">Toggle Walls</button>
<button id="resetHigh" class="btn">Reset High</button>
</div>
</aside>
</div>
</div>
<script>
// --- Config ---
const tile = 24; // px per tile
const cols = 28; // 28 * 24 = 672 wide area
const rows = 18; // 18 * 24 = 432 high area
const border = 2 * tile; // inner padding for walls
const baseSpeed = 120; // ms per tick (gets faster)
const levelEvery = 5; // foods per level
// --- Canvas setup ---
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
canvas.width = cols * tile + border * 2;
canvas.height = rows * tile + border * 2;
// --- UI refs ---
const scoreEl = document.getElementById('score');
const highEl = document.getElementById('high');
const levelEl = document.getElementById('level');
const pauseBtn = document.getElementById('pauseBtn');
const restartBtn = document.getElementById('restartBtn');
const slowBtn = document.getElementById('slowBtn');
const fastBtn = document.getElementById('fastBtn');
const toggleWallsBtn = document.getElementById('toggleWalls');
const resetHighBtn = document.getElementById('resetHigh');
document.querySelectorAll('[data-dir]').forEach(b => b.addEventListener('click', () => onDirection(b.dataset.dir)));
// --- State ---
let snake, dir, nextDir, food, score, level, wallsOn, tick, speed, paused, over;
const highScore = () => Number(localStorage.getItem('snakeHigh') || 0);
const setHighScore = (v) => localStorage.setItem('snakeHigh', String(v));
function reset() {
snake = [
{x: Math.floor(cols/2), y: Math.floor(rows/2)},
{x: Math.floor(cols/2)-1, y: Math.floor(rows/2)},
];
dir = {x:1,y:0};
nextDir = {...dir};
score = 0;
level = 1;
speed = baseSpeed;
wallsOn = true;
paused = false;
over = false;
spawnFood();
updateHUD();
loop();
}
function updateHUD(){
scoreEl.textContent = score;
highEl.textContent = highScore();
levelEl.textContent = level;
pauseBtn.textContent = paused ? 'Resume' : 'Pause';
toggleWallsBtn.textContent = wallsOn ? 'Disable Walls' : 'Enable Walls';
}
function spawnFood(){
do {
food = { x: Math.floor(Math.random()*cols), y: Math.floor(Math.random()*rows) };
} while (snake.some(s => s.x===food.x && s.y===food.y));
}
function drawBoard(){
// outer background already stylish; draw inner playfield
const x0 = border, y0 = border, w = cols*tile, h = rows*tile;
// playfield
const grd = ctx.createLinearGradient(0,y0,0,y0+h);
grd.addColorStop(0, '#0c1530'); grd.addColorStop(1, '#0a1026');
ctx.fillStyle = grd; ctx.fillRect(x0, y0, w, h);
// decorative grid dots
ctx.fillStyle = 'rgba(255,255,255,.04)';
for(let y=0;y<rows;y++){
for(let x=0;x<cols;x++){
ctx.fillRect(x0 + x*tile + tile/2 - 1, y0 + y*tile + tile/2 - 1, 2, 2);
}
}
// walls
if (wallsOn){
ctx.fillStyle = '#3b82f6';
ctx.fillRect(x0-4, y0-4, w+8, 4); // top
ctx.fillRect(x0-4, y0+h, w+8, 4); // bottom
ctx.fillRect(x0-4, y0, 4, h); // left
ctx.fillRect(x0+w, y0, 4, h); // right
}
}
function drawSnake(){
for(let i=0;i<snake.length;i++){
const s = snake[i];
const px = border + s.x*tile;
const py = border + s.y*tile;
const head = i===0;
if(head){
// head gradient
const g = ctx.createLinearGradient(px, py, px, py+tile);
g.addColorStop(0, '#6ee7ff');
g.addColorStop(1, '#a78bfa');
ctx.fillStyle = g;
} else {
ctx.fillStyle = 'rgba(167,139,250,.85)';
}
ctx.beginPath();
ctx.roundRect(px+3, py+3, tile-6, tile-6, 6);
ctx.fill();
}
}
function drawFood(){
const px = border + food.x*tile + tile/2;
const py = border + food.y*tile + tile/2;
ctx.fillStyle = '#34d399';
ctx.beginPath();
ctx.arc(px, py, tile*0.32, 0, Math.PI*2);
ctx.fill();
// glow
ctx.beginPath();
ctx.arc(px, py, tile*0.55, 0, Math.PI*2);
ctx.fillStyle = 'rgba(52,211,153,.14)';
ctx.fill();
}
function step(){
if (paused || over) return;
// apply nextDir (prevent instant reversal)
if ((nextDir.x !== -dir.x) || (nextDir.y !== -dir.y)) dir = nextDir;
// new head
const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y};
// wrap or collide with walls
if (wallsOn){
if (head.x<0 || head.x>=cols || head.y<0 || head.y>=rows){
return gameOver();
}
} else {
head.x = (head.x + cols) % cols;
head.y = (head.y + rows) % rows;
}
// self-collision
if (snake.some((s, i) => i>0 && s.x===head.x && s.y===head.y)) return gameOver();
snake.unshift(head);
// eat or move
if (head.x===food.x && head.y===food.y){
score++;
if (score > highScore()) setHighScore(score);
// level up
if (score % levelEvery === 0){
level++;
speed = Math.max(50, speed - 10);
}
spawnFood();
} else {
snake.pop();
}
draw();
tick = setTimeout(step, speed);
}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBoard();
drawFood();
drawSnake();
updateHUD();
}
function loop(){
clearTimeout(tick);
draw();
tick = setTimeout(step, speed);
}
function onDirection(which){
const map = {up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}};
nextDir = map[which] || nextDir;
}
function gameOver(){
over = true;
updateHUD();
// overlay
ctx.fillStyle = 'rgba(0,0,0,.55)';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '700 28px Inter, system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Game Over', canvas.width/2, canvas.height/2 - 8);
ctx.font = '500 16px Inter, system-ui, sans-serif';
ctx.fillText('Press Restart to play again', canvas.width/2, canvas.height/2 + 18);
}
// --- Input ---
window.addEventListener('keydown', (e)=>{
const k = e.key.toLowerCase();
if (k==='arrowup' || k==='w') onDirection('up');
else if (k==='arrowdown' || k==='s') onDirection('down');
else if (k==='arrowleft' || k==='a') onDirection('left');
else if (k==='arrowright' || k==='d') onDirection('right');
else if (k===' ') { paused = !paused; updateHUD(); if (!paused) loop(); }
});
pauseBtn.addEventListener('click', ()=>{ paused = !paused; updateHUD(); if (!paused) loop(); });
restartBtn.addEventListener('click', ()=> reset());
slowBtn.addEventListener('click', ()=>{ speed = Math.min(220, speed+20); });
fastBtn.addEventListener('click', ()=>{ speed = Math.max(40, speed-20); });
toggleWallsBtn.addEventListener('click', ()=>{ wallsOn = !wallsOn; updateHUD(); });
resetHighBtn.addEventListener('click', ()=>{ setHighScore(0); updateHUD(); });
// Polyfill for roundRect on older browsers
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
this.closePath();
return this;
}
}
reset();
</script>
</body>
</html>
Step 3: Deploy via Advanced Tools (Kudu)
💡 Why Kudu?
Kudu is the engine behind App Service that lets you browse/edit the site file system, run commands, and view logs—right from the browser.
Steps
In your App Service, go to Development Tools → Advanced Tools → Go
If you see
hostingstart.html
, delete it (otherwise it may overrideindex.html
).Click the pencil icon to create/edit a file named
index.html
✅ Your website now serves the Snake game from wwwroot/index.html
.
Step 4: Open Your Public URL
- Go back to your App Service Overview page.
Under Default domain, click the link (e.g.,
https://console.azurewebsites.net
or similar)
🛠️ Troubleshooting
- If you still see the Azure default page, make sure
hostingstart.html
is deleted and your file is named exactlyindex.html
. - Clear cache or open in a private window if changes don’t appear.
Step 5: Use Deployment Slots (Blue-Green Lite)
💡 What are Deployment Slots?
Slots let you have a staging site running alongside production. You can route a small percentage of real traffic to test your changes, then swap when you’re confident.
Steps
In your App Service, go to Deployment slots
Back on Deployment slots, select Traffic and assign 6% to
New Feature
. Save
- Now 6% of users see the slot version; the rest stay on production.
- When satisfied, choose Swap → source:
New Feature
→ target:Console
→ Start swap
✅ You safely validated the change, routed a small slice of traffic, and swapped the update into production without downtime.
Conclusion
You just:
- Created a Windows App Service with .NET 9 (STS) and a custom plan.
- Deployed a modern HTML/CSS/JS Snake game via Kudu.
- Practiced progressive exposure using Deployment Slots, sending 6% of traffic before a swap.
This is the same muscle memory you’ll use for real apps—feature testing, safe rollouts, and fast rollbacks all without touching servers.
🔑 Key takeaway: With Azure App Service + Kudu + Deployment Slots, you ship faster and safer experiment in a slot, route a trickle of traffic, then swap with confidence.
Top comments (0)