Scaleway Serverless Containers

Scaleway Serverless Container deployment via Pulumi, Nix builds, Bun runtime fix, cold start behavior, and error state diagnosis

Overview

SanMarcSoft uses Scaleway Serverless Containers (fr-par region) for backend services. Containers are deployed via Pulumi using the pulumi-scaleway-lib shared library. Images are pushed to Scaleway Container Registry via Skopeo (zero Docker daemon).

Architecture

flake.nix (Nix build)
  -> docker-archive tarball
    -> skopeo copy to rg.fr-par.scw.cloud/sanmarcsoft/
      -> Pulumi provisions Scaleway Serverless Container
        -> Container pulls from registry on deploy

Prerequisites

  • Scaleway API keys: pass sanmarcsoft/scaleway/access-key and pass sanmarcsoft/scaleway/api-secret
  • Pulumi state backend configured for Scaleway Object Storage
  • Skopeo installed (available in Nix dev shell)
  • Nix with flakes enabled

Procedure: Deploy a Service

Step 1: Build the OCI Image

1
2
cd /path/to/service
nix build .#packages.x86_64-linux.oci-image

This produces a docker-archive tarball at ./result.

Step 2: Push to Scaleway Container Registry

1
2
3
4
5
6
7
8
SCW_SECRET=$(pass sanmarcsoft/scaleway/api-secret)
IMAGE_NAME="service-name"
TAG="testing"

skopeo copy \
  docker-archive:./result \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/${IMAGE_NAME}:${TAG}" \
  --dest-creds "nologin:${SCW_SECRET}"

Important: The Scaleway Container Registry uses nologin as the username and the Scaleway API secret key as the password.

Step 3: Deploy via Pulumi

1
2
cd infra
pulumi up --stack <environment>

The Pulumi stack uses the ContainerService component from pulumi-scaleway-lib which provisions:

  • Scaleway Serverless Container namespace
  • Container deployment referencing the registry image
  • Environment variables and secrets
  • Custom domain CNAME (if configured)

Step 4: Verify Deployment

1
2
3
4
5
6
7
8
# Check container status via Scaleway CLI
scw container container list
scw container container get <container-id>

# Or via the Scaleway API
SCW_TOKEN=$(pass sanmarcsoft/scaleway/api-secret)
curl -s -H "X-Auth-Token: ${SCW_TOKEN}" \
  "https://api.scaleway.com/containers/v1beta1/regions/fr-par/containers" | jq '.containers[] | {name, status, domain_name}'

Container Configuration

Environment Variables

Environment variables are set in the Pulumi stack configuration. Secrets should use pulumi config set --secret.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// In infra/index.ts
const service = new ContainerService("my-service", {
  image: `rg.fr-par.scw.cloud/sanmarcsoft/my-service:${tag}`,
  port: 8080,
  minScale: 0,
  maxScale: 5,
  memoryLimit: 256,  // MB
  cpuLimit: 140,     // millicores
  environmentVariables: {
    NODE_ENV: "production",
    PORT: "8080",
  },
  secretEnvironmentVariables: {
    API_KEY: config.requireSecret("api-key"),
  },
});

Bun Runtime Fix

Scaleway Serverless Containers with Bun runtime may fail to start if the entrypoint uses #!/usr/bin/env bun. In Nix-built images, shebangs are rewritten to absolute Nix store paths, which resolves this issue. However, if using traditional Docker builds:

Problem: Container starts but crashes immediately with “bun: not found”

Solution: Use an explicit entrypoint in the container config:

1
2
3
config = {
  Cmd = [ "${pkgs.bun}/bin/bun" "${appFiles}/app/index.js" ];
};

Cold Start Behavior

Scaleway Serverless Containers scale to zero by default (minScale: 0). Cold starts can take 2-10 seconds depending on image size.

For latency-sensitive services: Set minScale: 1 in the Pulumi config to keep at least one instance warm.

For cost optimization: Keep minScale: 0 and implement a health check endpoint that can be pinged by an uptime monitor to keep the container warm during business hours.

Error State Diagnosis

Container Status: “error”

When a Scaleway container shows status “error”:

  1. Check container logs:
1
scw container container get <container-id> -o json | jq '.error_message'
  1. Common causes:

    • Image not found in registry (wrong tag or deleted)
    • Port mismatch (container exposes different port than configured)
    • Entrypoint crash (missing binary, permission error)
    • Memory limit exceeded during startup
  2. Resolution steps:

    • Verify image exists: skopeo inspect docker://rg.fr-par.scw.cloud/sanmarcsoft/<name>:<tag> --creds "nologin:$(pass sanmarcsoft/scaleway/api-secret)"
    • Re-push image if missing
    • Re-deploy with pulumi up
    • If container is stuck in error state, delete and recreate via Pulumi

Container Status: “ready” but returning 502

The container is deployed but the application inside is not serving correctly:

  1. Check the port configuration matches the application’s listening port
  2. Verify health check endpoint responds
  3. Check if the application requires environment variables that are not set
  4. Review application logs via Scaleway console or API

Registry Management

List Images in Registry

1
2
3
4
SCW_SECRET=$(pass sanmarcsoft/scaleway/api-secret)
skopeo list-tags \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/<image-name>" \
  --creds "nologin:${SCW_SECRET}"

Inspect Image Metadata

1
2
3
skopeo inspect \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/<image-name>:<tag>" \
  --creds "nologin:${SCW_SECRET}" | jq '{Digest, Created, Architecture}'

Retag Image (testing -> production)

1
2
3
4
5
skopeo copy \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/<name>:testing" \
  "docker://rg.fr-par.scw.cloud/sanmarcsoft/<name>:production" \
  --src-creds "nologin:${SCW_SECRET}" \
  --dest-creds "nologin:${SCW_SECRET}"

Troubleshooting

  • “unauthorized” on push: Verify Scaleway API secret is current. Regenerate if needed via Scaleway console.
  • Image architecture mismatch: Ensure nix build targets x86_64-linux. Scaleway containers run on x86_64.
  • Container stuck in “creating”: Wait 2-3 minutes. If still stuck, check Scaleway status page for regional issues.
  • DNS not resolving: After Pulumi deploy, CNAME records may take a few minutes to propagate. Use dig to verify.