Migration Guides

Step-by-step guides for switching from US big tech to European alternatives. We're building these out — request one and we'll prioritise it.

All Guides

From Azure App ServicesHetzner VMComing soon
From Google Kubernetes Engine (GKE)Hetzner VMComing soon
From SupabaseHetzner VMComing soon

Don't see what you need?

Request a specific guide
Complete guide

Migrate from Azure Static Web Apps to Scaleway Object Storage

A step-by-step guide to moving your static site from Microsoft Azure to European infrastructure — based on a real migration we did for this site.

Estimated time: 1–2 hoursDifficulty: Beginner–IntermediateLast updated: March 2026

Overview

Azure Static Web Apps is a convenient way to host static sites and SPAs, but your data is hosted on Microsoft infrastructure in the US (or US-controlled regions) and subject to US law — including the CLOUD Act, which allows US authorities to demand access to data held by US companies regardless of where it is stored.

Scaleway is a French cloud provider (part of the Iliad Group) with data centres in Paris and Amsterdam. Your data stays in the EU on infrastructure owned by a European company, making it GDPR-native by design. For static site hosting, Scaleway Object Storage with its website hosting feature is a direct, cost-effective replacement for Azure Static Web Apps.

Cost Comparison

Azure Static Web AppsScaleway + BunnyCDN
StorageIncludedScaleway: ~€0.01/GB/month (pay-as-you-go, no free tier)
Bandwidth100 GB/month (Free tier)Scaleway egress: ~€0.01/GB. BunnyCDN: ~€0.01/GB (EU zones)
Typical monthly cost$0 (Free) or $9/month (Standard)~€3–15/month total depending on traffic
CDNAzure CDN (included)BunnyCDN: from ~€1/month + per-GB. Scaleway Edge Services: €0.04/GB
SSLIncluded (managed)BunnyCDN: included (auto Let's Encrypt). Scaleway Edge: included
Custom domains2 (Free), 5 (Standard)Unlimited (via BunnyCDN)
Build & deployBuilt-in CI/CD from GitHubGitHub Actions (self-configured)
Data residencyUS-owned, data subject to CLOUD ActEU-owned: Scaleway (FR) + BunnyCDN (SI)

Real-world cost: For a low-traffic static site like this one, the Scaleway + BunnyCDN combination runs roughly €3–5/month total — Scaleway charges per GB stored and egressed, BunnyCDN charges per GB of CDN traffic. Both are paid services with no free tier, but at low traffic volumes the bills stay very small. Azure's free tier covers the basics for a single app, but the moment you need the Standard plan ($9/month per app) for custom auth or more domains, the EU stack becomes significantly cheaper. The key difference: with Scaleway + BunnyCDN you get transparent per-usage pricing and full EU data residency, rather than per-app flat fees on US-owned infrastructure.

Prerequisites

  • A Scaleway account with Object Storage activated
  • s3cmd installed and configured with your Scaleway API keys, or the AWS CLI configured for Scaleway
  • Access to your Azure Static Web App source code (Git repository)
  • DNS access for your custom domain (if applicable)
  • Node.js and npm (or your site's build toolchain) installed locally

Step 1: Export your static site

Azure Static Web Apps builds your site from source — it doesn't store your compiled files separately. You'll need your source repository to produce the build output.

Clone your repository and build locally:

# Clone your repo (if you haven't already)
git clone https://github.com/your-org/your-site.git
cd your-site

# Install dependencies
npm install

# Build the static site
npm run build

Your compiled output will be in dist/, build/, or out/ depending on your framework (Vite, Create React App, Next.js export, etc.). Check your package.json or framework docs if you're unsure.

Step 2: Create a Scaleway Object Storage bucket

  1. Log in to the Scaleway Console
  2. Navigate to Object Storage in the left sidebar
  3. Click Create Bucket
  4. Choose a region: nl-ams (Amsterdam) or fr-par (Paris)
  5. Name your bucket — this must be globally unique
  6. Set the bucket visibility to Public
Apex domain gotcha: If you plan to serve your site from an apex domain (e.g., example.com rather than www.example.com), you must name your bucket exactly the same as your domain — for example, example.com. This is how S3-compatible website hosting maps bucket names to hostnames. We learned this the hard way when setting up cloudinfraatlas.eu: the bucket had to be named cloudinfraatlas.eu for the website endpoint to work correctly with our domain.

Alternatively, create it via the CLI:

# Name the bucket after your domain if using apex hosting
s3cmd mb s3://example.com --region=nl-ams

Step 3: Configure the bucket for static website hosting

Enable the website hosting feature on your bucket. This tells Scaleway to serve files as a website and handle SPA routing by falling back to index.html:

s3cmd ws-create --ws-index=index.html --ws-error=index.html s3://example.com/

Setting --ws-error=index.html ensures that requests for unknown paths are routed to your index.html, which is essential for single-page applications using client-side routing.

Step 4: Upload your built site

Sync your build output to the bucket. This uploads all files, sets them as publicly readable, removes files from the bucket that no longer exist locally, and auto-detects MIME types:

s3cmd sync --acl-public --delete-removed \
  --guess-mime-type --no-mime-magic \
  dist/ s3://example.com/

Replace dist/ with your actual build output directory. After the upload completes, your site is live at the Scaleway bucket website endpoint:

http://example.com.s3-website.nl-ams.scw.cloud

Step 5: The HTTPS and apex domain problem

This is where we ran into real problems setting up cloudinfraatlas.eu, and it's worth documenting because it's the single biggest friction point of this migration.

Problem 1: Scaleway S3 website hosting is HTTP-only

The Scaleway Object Storage website endpoint (*.s3-website.nl-ams.scw.cloud) only serves over HTTP. There is no built-in HTTPS. In 2026, every site needs HTTPS — browsers show security warnings without it, and Google ranks HTTPS pages higher.

Problem 2: Scaleway Edge Services can't target an apex domain

Scaleway offers Edge Services as an add-on to Object Storage buckets, which provides HTTPS termination and caching. We configured this, and it worked — but only for subdomains like www.cloudinfraatlas.eu.

For the apex domain (cloudinfraatlas.eu), most DNS providers require an A record (an IP address), not a CNAME. Scaleway Edge Services only provides a CNAME endpoint, and Scaleway's own DNS doesn't support ALIAS/ANAME records that would flatten a CNAME at the apex. This is a known limitation of S3-compatible hosting across most providers (AWS has the same issue unless you use Route 53 or CloudFront).

Problem 3: The solution — BunnyCDN

After trying several approaches, we solved this by placing BunnyCDN (a European CDN headquartered in Slovenia) in front of the Scaleway bucket. BunnyCDN provides:

  • A static IP address for the A record (solving the apex domain problem)
  • Automatic Let's Encrypt SSL certificate provisioning
  • Edge caching across European PoPs for faster load times
  • Still EU-owned infrastructure — no US jurisdiction

How the architecture evolved

Before (Azure)
UserAzure CDN + SSLAzure Static Web Apps (US)
Attempt 1 — Scaleway only (HTTP, no apex)
UserNo HTTPSScaleway S3 Website (EU)
Attempt 2 — Scaleway Edge Services (HTTPS, no apex)
UserScaleway Edge (HTTPS)Scaleway S3 (EU)
Works for www.example.com, but NOT for apex domain (example.com)
Final — BunnyCDN + Scaleway (HTTPS + apex + caching)
UserBunnyCDN (SI) — SSL + A recordScaleway S3 (FR/NL)
Full HTTPS, apex domain, edge caching — 100% EU infrastructure

This three-layer architecture is actually an improvement over Azure — you get edge caching with a European CDN, better control over cache headers, and the entire chain is EU-owned. The downside is setup complexity: Azure abstracts all of this away, while with Scaleway you configure each layer yourself.

Step 6: DNS configuration

With BunnyCDN in place, configure your DNS records:

# Apex domain — A record pointing to BunnyCDN IP
@    A      185.93.2.248

# WWW subdomain — CNAME to BunnyCDN pull zone
www  CNAME  your-pullzone.b-cdn.net

In BunnyCDN's dashboard, set up a Pull Zone with your Scaleway bucket website endpoint as the origin:

  1. Create a Pull Zone with origin URL: http://example.com.s3-website.nl-ams.scw.cloud
  2. Add example.com and www.example.com as custom hostnames
  3. Enable Force SSL on each hostname — BunnyCDN auto-provisions Let's Encrypt certificates
  4. Wait 1–5 minutes for the certificate to issue (it can only issue after DNS points to BunnyCDN)
Important: Remove any old DNS records pointing to Azure or other providers before adding the BunnyCDN records. Having both active simultaneously will cause intermittent SSL errors as browsers randomly resolve to one or the other.

Step 7: Set up CI/CD

Automate deployments so your site updates whenever you push to main. Here's a real-world GitHub Actions workflow based on the one we use for Cloud Infra Atlas:

# .github/workflows/deploy.yml
name: Deploy to Scaleway

on:
  push:
    branches: [main]
    paths:
      - "app/frontend/**"
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm run build

      - name: Install s3cmd
        run: sudo apt-get update && sudo apt-get install -y s3cmd

      - name: Deploy to Scaleway
        run: |
          # Upload static assets (JS, CSS, images) with long cache
          s3cmd sync --acl-public --delete-removed \
            --guess-mime-type --no-mime-magic \
            --exclude '*.html' \
            --add-header='Cache-Control:public, max-age=31536000, immutable' \
            --host=s3.nl-ams.scw.cloud \
            --host-bucket='%(bucket)s.s3.nl-ams.scw.cloud' \
            dist/ s3://${{ secrets.SCW_BUCKET_NAME }}/

          # Upload HTML with short cache for freshness
          s3cmd sync --acl-public \
            --guess-mime-type --no-mime-magic \
            --include '*.html' \
            --add-header='Cache-Control:public, max-age=3600' \
            --host=s3.nl-ams.scw.cloud \
            --host-bucket='%(bucket)s.s3.nl-ams.scw.cloud' \
            dist/ s3://${{ secrets.SCW_BUCKET_NAME }}/
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.SCW_ACCESS_KEY }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.SCW_SECRET_KEY }}

Add SCW_ACCESS_KEY, SCW_SECRET_KEY, and SCW_BUCKET_NAME as secrets in your GitHub repository settings. Generate API keys in the Scaleway IAM console.

Pro tip: Split your uploads into two passes — static assets (JS, CSS, images, fonts) with long cache headers (max-age=31536000, immutable), and HTML files with short cache (max-age=3600). This gives you instant cache busting for content changes while keeping assets fast. Vite's content-hashed filenames make this safe — old assets are never overwritten, they just get new filenames.

Problems We Encountered

This migration wasn't entirely smooth. Here are the real issues we hit and how we solved them, so you don't have to figure them out yourself:

1. Bucket naming requirement for apex domains

Scaleway's S3-compatible website hosting requires the bucket name to match your domain exactly for apex domain hosting. We initially named our bucket something generic, then had to recreate it as cloudinfraatlas.eu. This is an S3 convention dating back to AWS — the website endpoint uses the bucket name as the hostname prefix.

2. Scaleway Edge Services doesn't support apex domains

Scaleway's Edge Services add-on provides HTTPS and caching for Object Storage buckets. It worked perfectly for www.cloudinfraatlas.eu, but we couldn't point the apex domain (cloudinfraatlas.eu) to it. Edge Services only exposes a CNAME endpoint, and Scaleway's DNS doesn't support ALIAS/ANAME record flattening. This is a genuine gap in Scaleway's product — AWS solves this with CloudFront + Route 53, but Scaleway doesn't have an equivalent pairing yet.

3. DNS record conflict causing SSL errors

When we added BunnyCDN, we initially kept the old Scaleway A record active alongside the new BunnyCDN record. This meant the domain resolved to two different IPs — one with a valid SSL cert (BunnyCDN) and one without (Scaleway S3). Browsers randomly picked one, causing intermittent ERR_SSL_PROTOCOL_ERROR for about half of visitors. The fix was simple: remove the old A record before adding the new one.

4. BunnyCDN SSL certificate timing

After switching DNS to BunnyCDN, the site showed SSL errors for a few minutes because the Let's Encrypt certificate hadn't been provisioned yet. BunnyCDN can only issue the certificate after DNS points to them (for domain validation). This is a 1–5 minute gap that's unavoidable. If you're migrating a live site, either do it during low-traffic hours or set a short DNS TTL (60–300 seconds) beforehand so the transition is fast.

5. Ubuntu package name change in CI

Our GitHub Actions workflow installed Puppeteer system dependencies including libasound2. When Ubuntu updated to Noble (24.04), this package was renamed to libasound2t64, breaking the build. A small thing, but it blocked deploys until we noticed. Always pin or check your CI runner's OS version.

What you gain

  • Data stays in the EU — hosted in France or the Netherlands, on European soil
  • GDPR-native infrastructure — Scaleway is built for EU compliance from the ground up
  • No US CLOUD Act exposure — Scaleway is French-owned (Iliad Group), not subject to US data access demands
  • European ownership end-to-end — Scaleway (FR) + BunnyCDN (SI) = entirely EU-owned stack
  • Edge caching — BunnyCDN adds performance your Azure Static Web App didn't have
  • Cost-effective — Scaleway Object Storage is typically cheaper than Azure Static Web Apps for sites with moderate traffic
  • Full control — you own the deployment pipeline, cache headers, and infrastructure decisions