Skip to content

aknostic/scaleway-project-scaffold

PROJECT_NAME Infrastructure

Kubernetes infrastructure on Scaleway using OpenTofu and Flux GitOps.

What you get: A production-ready Kubernetes cluster with private nodes, GitOps-based deployments via Flux, automatic DNS record management, TLS certificate provisioning, and an Envoy-based ingress gateway with a static public IP.

Prerequisites

  • Scaleway account with Organization access
  • GitHub repository with fine-grained PAT (Contents + Administration)
  • CLI tools: scw, tofu, flux, kubectl, sops, age, gh

Quick Setup

1. Create Scaleway project and API key

First, ensure you have the Scaleway CLI configured with organization-level access:

# Initial CLI setup (if not already done)
scw init
# Use your organization-level API key from console.scaleway.com

Create a dedicated Scaleway project for resource isolation:

# Create new project (or use existing project ID)
scw account project create name=myproject
# Note the project ID from output

# Create IAM application for this project
scw iam application create name=myproject-infra

# Create API key with project scope
scw iam api-key create application-id=<app-id> default-project-id=<project-id>
# Note the access_key and secret_key from output

# Configure CLI with the new key
scw init
# Enter the access_key and secret_key when prompted

Important: The API key's default_project_id determines where resources are created. S3 buckets inherit project scope from the API key, not from bucket creation parameters.

2. Create repository from template

# Create your repo from the template
gh repo create myorg/myproject-infra --template __GITHUB_ORG__/scaleway-project-scaffold --public --clone
cd myproject-infra

# Get latest K8s version
scw k8s version list -o json | jq -r '.[0].name'

Replace all placeholders (the workflow will fail if any remain):

Placeholder Description Example
__PROJECT_NAME__ Your project identifier myproject
__DOMAIN__ Your DNS domain myproject.com
__GITHUB_ORG__ GitHub org or username myorg
__GITHUB_REPO__ Repository name myproject-infra
__K8S_VERSION__ Kubernetes version (from above) 1.32
# Replace placeholders (macOS)
find . -type f \( -name "*.yaml" -o -name "*.tf" -o -name "*.yml" -o -name "*.md" \) | xargs sed -i '' 's/__PROJECT_NAME__/myproject/g'
find . -type f \( -name "*.yaml" -o -name "*.tf" -o -name "*.yml" -o -name "*.md" \) | xargs sed -i '' 's/__DOMAIN__/myproject.com/g'
find . -type f \( -name "*.yaml" -o -name "*.tf" -o -name "*.yml" -o -name "*.md" \) | xargs sed -i '' 's/__GITHUB_ORG__/myorg/g'
find . -type f \( -name "*.yaml" -o -name "*.tf" -o -name "*.yml" -o -name "*.md" \) | xargs sed -i '' 's/__GITHUB_REPO__/myproject-infra/g'
find . -type f \( -name "*.yaml" -o -name "*.tf" -o -name "*.yml" -o -name "*.md" \) | xargs sed -i '' 's/__K8S_VERSION__/1.32/g'

# Linux: use sed -i instead of sed -i ''

# Verify all placeholders replaced (should return nothing)
grep -r "__[A-Z_]*__" --include="*.tf" --include="*.yaml" --include="*.yml" .

3. Generate SOPS age key

age-keygen -o age.key
# Note the public key (age1...) from output

# Update .sops.yaml with your public key (this placeholder is separate from Step 2)
sed -i '' 's/__SOPS_AGE_PUBLIC_KEY__/age1yourpublickeyhere/g' .sops.yaml
Placeholder Description
__SOPS_AGE_PUBLIC_KEY__ Your age public key (starts with age1...)

4. Create tfstate bucket

scw object bucket create name=myproject-tfstate region=fr-par

5. Configure Scaleway credentials secrets

Two secrets need your Scaleway credentials (same values for both):

# Edit external-dns credentials
vi gitops/infrastructure/core/external-dns-secret.yaml

# Edit cert-manager credentials (for DNS-01 certificate validation)
vi gitops/infrastructure/core/scaleway-credentials-certmanager.yaml

# Encrypt both with SOPS
sops --encrypt --in-place gitops/infrastructure/core/external-dns-secret.yaml
sops --encrypt --in-place gitops/infrastructure/core/scaleway-credentials-certmanager.yaml

6. Set GitHub secrets

Use the project-scoped API key created in step 1:

gh secret set SCW_ACCESS_KEY --body "SCWXXXXXXXXX"
gh secret set SCW_SECRET_KEY --body "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
gh secret set SCW_DEFAULT_ORGANIZATION_ID --body "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
gh secret set SCW_DEFAULT_PROJECT_ID --body "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
gh secret set FLUX_GITHUB_TOKEN --body "github_pat_..."
gh secret set SOPS_AGE_KEY < age.key

7. Create GitHub environment

Create a "production" environment for the deployment approval gate:

  1. Go to repository Settings > Environments
  2. Click "New environment"
  3. Name it production
  4. Optionally configure required reviewers for manual approval

Or skip approval by using skip_approval: true when triggering the workflow.

8. Commit and deploy

git add .
git commit -m "Configure project"
git push

gh workflow run infrastructure.yml -f action=deploy

Post-Deploy

  1. Download kubeconfig:
    • From workflow run artifacts, or
    • From Scaleway Console (Kubernetes → Cluster → Download kubeconfig) if you have console access
  2. Verify Flux: flux get kustomizations
  3. Verify ClusterIssuer: kubectl get clusterissuer letsencrypt-prod
  4. Add HTTPRoutes to expose services (certificates are issued automatically via DNS-01)

Customization

Adding node pools

Use the node_pools variable in infrastructure/opentofu/environments/main/variables.tf:

variable "node_pools" {
  default = [
    { name = "pool-1", zone = "fr-par-1", size = 1 },
    { name = "pool-2", zone = "fr-par-2", size = 1 }
  ]
}

Each pool supports: name, zone, size, min_size, max_size, node_type.

Adding observability

Add Grafana, Prometheus, Loki stacks to gitops/infrastructure/observability/.

Multi-environment

Copy infrastructure/opentofu/environments/main/ to create staging/prod environments.


Troubleshooting

Unresolved placeholders

If you see errors about __PROJECT_NAME__ or similar, placeholders weren't fully replaced. Verify:

# Check for remaining placeholders
grep -r "__[A-Z_]*__" --include="*.tf" --include="*.yaml" --include="*.yml" .

IAM permission errors

Project-scoped API keys need specific permissions. If you see insufficient permissions errors:

Error Required Permission
write application IAMManager (for creating per-service IAM)
read project ProjectReadOnly (for data source lookups)

Simplest fix: Use a single shared API key with full project access rather than per-service IAM applications.

Zone availability for instance types

Not all instance types are available in all zones. Common issue:

Error: commercial_type does not respect constraint, commercial type not available in this zone

Safe defaults: fr-par-1 and fr-par-2 have the widest instance availability. Avoid fr-par-3 for DEV1/PRO2 types unless you verify availability first:

scw instance server-type list zone=fr-par-1

Flux CRD timing issues

If you see errors like no matches for kind "ClusterIssuer", the CRD isn't installed yet. Ensure resources that depend on CRDs (like ClusterIssuer) are in a Kustomization that dependsOn the operator installation (cert-manager).


Appendix: European Sovereignty

This scaffold prioritizes European digital sovereignty using EU-owned infrastructure and open source tools.

Sovereign by Default

Component Provider Governance
Cloud Scaleway French (Iliad Group), EU data centers
IaC OpenTofu Linux Foundation, MPL 2.0
GitOps Flux CD CNCF Graduated
Ingress Envoy Gateway CNCF Graduated
Certificates cert-manager CNCF Graduated
Secrets SOPS/age Mozilla (SOPS), Filippo Valsorda (age)
State Scaleway Object Storage French, fr-par region

Known Gaps

  • GitHub/GitHub Actions - US-owned, subject to CLOUD Act
  • Container registries - docker.io, registry.k8s.io are US-hosted
  • Let's Encrypt - US-based CA (ISRG)

EU-Sovereign Alternatives

For strict sovereignty requirements:

Gap Alternative Notes
GitHub GitLab (gitlab.com) Netherlands-based, or self-host
GitHub Gitea Self-host on Scaleway
GitHub Actions GitLab CI/CD Included with GitLab
Container images Scaleway Container Registry rg.fr-par.scw.cloud/project/
Let's Encrypt Buypass (Norway) EU-based ACME CA

Mirror images to Scaleway:

# Pull, tag, push to Scaleway Container Registry
docker pull quay.io/jetstack/cert-manager-controller:v1.17.2
docker tag quay.io/jetstack/cert-manager-controller:v1.17.2 rg.fr-par.scw.cloud/__PROJECT_NAME__/cert-manager-controller:v1.17.2
docker push rg.fr-par.scw.cloud/__PROJECT_NAME__/cert-manager-controller:v1.17.2

Related Projects

About

For a good Scaleway start

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages