diff --git a/README.md b/README.md
index ded4547..7ead77c 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ Table of Contents:
| Example | Runtime | Deployment |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ---------------------- |
+|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|------------------------|
| **[Badge PHP](functions/badge-php/README.md)**
A PHP function to generate repository badges. | php82 | [Serverless Framework] |
| **[CORS Go](functions/cors-go/README.md)**
A Go function which allows CORS requests. | go122 | [Serverless Framework] |
| **[CORS Node](functions/cors-node/README.md)**
A Node function which allows CORS requests. | node18 | [Serverless Framework] |
@@ -66,27 +66,28 @@ Table of Contents:
### 📦 Containers
-| Example | Language | Deployment |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------------------- |
-| **[Container Bash Script](containers/bash-scheduled-job/README.md)**
A Bash script runnning on a schedule using serverless containers. | Bash | [Serverless Framework] |
-| **[Function Handler Java](containers/function-handler-java/README.md)**
A Java function handler deployed on CaaS. | Java | [Serverless Framework] |
-| **[NGINX CORS Private](containers/nginx-cors-private-python/README.md)**
An NGINX proxy to allow CORS requests to a private container. | Python Flask | [Terraform] |
-| **[NGINX hello world](containers/nginx-hello-world/README.md)**
A minimal example running the base NGINX image in a serverless container. | N/A | [Serverless Framework] |
-| **[Python hello world](containers/python-hello-world/README.md)**
A minimal example running a Flask HTTP server in a serverless container. | Python | [Serverless Framework] |
-| **[Python S3 upload](containers/python-s3-upload/README.md)**
A Python + Flask HTTP server that receives file uploads and writes them to S3. | Python | [Terraform] |
-| **[Terraform NGINX hello world](containers/terraform-nginx-hello-world/README.md)**
A minimal example running the base NGINX image in a serverless container deployed with Terraform. | N/A | [Terraform] |
-| **[Triggers with Terraform](containers/terraform-triggers/README.md)**
Configuring two SQS triggers, used to trigger two containers, one public, one private. | N/A | [Terraform] |
-| **[gRPC HTTP2 in Go](containers/grpc-http2-go/README.md)**
A Go gRPC Container using http2 | Go/Protobuf | [CLI] |
-| **[Rust hello world](containers/rust-hello-world)**
A simple Rust hello world Container | Rust | [CLI] |
-| **[.NET C#](containers/csharp-hello-world)**
A .NET C# Container hello world | C# .NET | [CLI] |
-| **[Ruby Hello World](containers/ruby-hello-world/)**
A simple Ruby Hello world Container | Ruby | [CLI] |
-| **[Deploy Memos app](containers/memos-terraform/)**
A journaling application with its database deployed with Terraform | Terraform | [Terraform] |
-| **[Metabase on VPC](containers/vpc-metabase/README.md)**
A Metabase instance running in a private network with a PostgreSQL database. | N/A | [Terraform] |
+| Example | Language | Deployment |
+|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|
+| **[Container Bash Script](containers/bash-scheduled-job/README.md)**
A Bash script runnning on a schedule using serverless containers. | Bash | [Serverless Framework] |
+| **[Function Handler Java](containers/function-handler-java/README.md)**
A Java function handler deployed on CaaS. | Java | [Serverless Framework] |
+| **[NGINX CORS Private](containers/nginx-cors-private-python/README.md)**
An NGINX proxy to allow CORS requests to a private container. | Python Flask | [Terraform] |
+| **[NGINX hello world](containers/nginx-hello-world/README.md)**
A minimal example running the base NGINX image in a serverless container. | N/A | [Serverless Framework] |
+| **[Python hello world](containers/python-hello-world/README.md)**
A minimal example running a Flask HTTP server in a serverless container. | Python | [Serverless Framework] |
+| **[Python S3 upload](containers/python-s3-upload/README.md)**
A Python + Flask HTTP server that receives file uploads and writes them to S3. | Python | [Terraform] |
+| **[Terraform NGINX hello world](containers/terraform-nginx-hello-world/README.md)**
A minimal example running the base NGINX image in a serverless container deployed with Terraform. | N/A | [Terraform] |
+| **[Triggers with Terraform](containers/terraform-triggers/README.md)**
Configuring two SQS triggers, used to trigger two containers, one public, one private. | N/A | [Terraform] |
+| **[gRPC HTTP2 in Go](containers/grpc-http2-go/README.md)**
A Go gRPC Container using http2 | Go/Protobuf | [CLI] |
+| **[Rust hello world](containers/rust-hello-world)**
A simple Rust hello world Container | Rust | [CLI] |
+| **[.NET C#](containers/csharp-hello-world)**
A .NET C# Container hello world | C# .NET | [CLI] |
+| **[Ruby Hello World](containers/ruby-hello-world/)**
A simple Ruby Hello world Container | Ruby | [CLI] |
+| **[Deploy Memos app](containers/memos-terraform/)**
A journaling application with its database deployed with Terraform | Terraform | [Terraform] |
+| **[Metabase on VPC](containers/vpc-metabase/README.md)**
A Metabase instance running in a private network with a PostgreSQL database. | N/A | [Terraform] |
+| **[MongoDB® on VPC](containers/vpc-mongodb/README.md)**
A MongoDB® instance running in a private network with a sample application connecting to it. | TypeScript on Deno | [Terraform] |
### ⚙️ Jobs
| Example | Language | Deployment |
-| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------- |
+|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------|
| **[Serverless Jobs Hello World](jobs/terraform-hello-world/README.md)**
An example of building a container image and running it as a Serverless Job using Terraform. | N/A | [Terraform]-[Console] |
| **[Serverless MLOps](jobs/ml-ops/README.md)**
An example of running a Serverless Machine Leaning workflow. | Python | [Terraform]-[Console]-[CLI] |
| **[Auto Snapshot Instances](jobs/instances-snapshot/README.md)**
Use Serverless Jobs to create snapshots of your instances | Go | [Console] |
@@ -97,7 +98,7 @@ Table of Contents:
### 💬 Messaging and Queueing
| Example | Services | Language | Deployment |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -------- | ----------- |
+|--------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------|-------------|
| **[Manage large message](mnq/large-messages/README.md)**
An example of infrastructure to manage large messages. | PaaS & S3 | Python | [Terraform] |
| **[Serverless scraping](mnq/serverless-scraping/README.md)**
An example of infrastructure to scrape the hackernews website. | PaaS & RDB | Python | [Terraform] |
| **[SNS Instances Notification System](mnq/sns-instances-notification-system/README.md)**
An example of infrastructure to use SNS with Instances. | PaaS & Instances | Golang | [Terraform] |
@@ -105,7 +106,7 @@ Table of Contents:
### 💜 Projects
| Example | Services | Language | Deployment |
-| ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | ---------------------- |
+|-------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|------------------------|
| **[Kong API Gateway](projects/kong-api-gateway/README.md)**
Deploying a Kong Gateway on containers to provide routing to functions. | CaaS & FaaS | Python | [Serverless Framework] |
| **[Serverless Gateway](https://github.com/scaleway/serverless-gateway)**
Our serverless gateway for functions and containers. | API Gateway | Python | [Python API Framework] |
| **[Monitoring Glaciers](projects/blogpost-glacier/README.md)**
A project to monitor glaciers and the impact of global warming. | S3 & RDB | Golang | [Serverless Framework] |
diff --git a/containers/vpc-mongodb/README.md b/containers/vpc-mongodb/README.md
new file mode 100644
index 0000000..8a5627e
--- /dev/null
+++ b/containers/vpc-mongodb/README.md
@@ -0,0 +1,47 @@
+# Serverless Containers with Scaleway Managed MongoDB®
+
+A simple Deno application demonstrating how to connect to a Scaleway Managed MongoDB® instance using Mongoose. It relies on Scaleway's Private Networks for secure communication.
+
+## Deploying
+
+### Prerequisites
+
+This Terraform pushes a Docker image to a Scaleway Container Registry before deploying the Serverless Container.
+
+Make sure to log in to the Scaleway Container Registry beforehand:
+
+```bash
+scw registry login
+```
+
+### Deploy with Terraform
+
+```bash
+terraform init
+terraform apply
+```
+
+That's it! You should be able to access the deployed Serverless Container's endpoint from the Scaleway Console.
+
+## Using the Application
+
+Once deployed, you can interact with the application using HTTP requests.
+
+```bash
+CONTAINER_URL="https://your-container-url"
+# Check MongoDB® connection
+curl $CONTAINER_URL/check_connection
+# Add some people
+curl $CONTAINER_URL/person/george
+curl $CONTAINER_URL/person/alice
+# List all people
+curl $CONTAINER_URL/people
+```
+
+## Cleaning Up
+
+Make sure to destroy the Terraform-managed infrastructure when you're done:
+
+```bash
+terraform destroy
+```
diff --git a/containers/vpc-mongodb/app/Dockerfile b/containers/vpc-mongodb/app/Dockerfile
new file mode 100644
index 0000000..7815e53
--- /dev/null
+++ b/containers/vpc-mongodb/app/Dockerfile
@@ -0,0 +1,11 @@
+# Build stage
+FROM denoland/deno:latest AS builder
+WORKDIR /app
+COPY . .
+RUN deno cache main.ts
+
+# Production stage
+FROM denoland/deno:latest
+WORKDIR /app
+COPY --from=builder /app .
+CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "--allow-write", "--allow-sys", "main.ts"]
diff --git a/containers/vpc-mongodb/app/deno.json b/containers/vpc-mongodb/app/deno.json
new file mode 100644
index 0000000..cba210d
--- /dev/null
+++ b/containers/vpc-mongodb/app/deno.json
@@ -0,0 +1,10 @@
+{
+ "tasks": {
+ "dev": "deno run --watch main.ts"
+ },
+ "imports": {
+ "@std/assert": "jsr:@std/assert@1",
+ "@std/random": "jsr:@std/random@^0.1.4",
+ "mongoose": "npm:mongoose@^8.20.1"
+ }
+}
diff --git a/containers/vpc-mongodb/app/deno.lock b/containers/vpc-mongodb/app/deno.lock
new file mode 100644
index 0000000..1812653
--- /dev/null
+++ b/containers/vpc-mongodb/app/deno.lock
@@ -0,0 +1,129 @@
+{
+ "version": "5",
+ "specifiers": {
+ "jsr:@std/assert@1": "1.0.16",
+ "jsr:@std/internal@^1.0.12": "1.0.12",
+ "jsr:@std/random@~0.1.4": "0.1.4",
+ "npm:mongoose@^8.20.1": "8.20.1"
+ },
+ "jsr": {
+ "@std/assert@1.0.16": {
+ "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532",
+ "dependencies": [
+ "jsr:@std/internal"
+ ]
+ },
+ "@std/internal@1.0.12": {
+ "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
+ },
+ "@std/random@0.1.4": {
+ "integrity": "91407b5c30d63b6570e5d7be8b341bf5941c55d5c6d6b9569a220b63ae03e2d9"
+ }
+ },
+ "npm": {
+ "@mongodb-js/saslprep@1.3.2": {
+ "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
+ "dependencies": [
+ "sparse-bitfield"
+ ]
+ },
+ "@types/webidl-conversions@7.0.3": {
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
+ },
+ "@types/whatwg-url@11.0.5": {
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "dependencies": [
+ "@types/webidl-conversions"
+ ]
+ },
+ "bson@6.10.4": {
+ "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="
+ },
+ "debug@4.4.3": {
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dependencies": [
+ "ms"
+ ]
+ },
+ "kareem@2.6.3": {
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q=="
+ },
+ "memory-pager@1.5.0": {
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
+ },
+ "mongodb-connection-string-url@3.0.2": {
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "dependencies": [
+ "@types/whatwg-url",
+ "whatwg-url"
+ ]
+ },
+ "mongodb@6.20.0": {
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
+ "dependencies": [
+ "@mongodb-js/saslprep",
+ "bson",
+ "mongodb-connection-string-url"
+ ]
+ },
+ "mongoose@8.20.1": {
+ "integrity": "sha512-G+n3maddlqkQrP1nXxsI0q20144OSo+pe+HzRRGqaC4yK3FLYKqejqB9cbIi+SX7eoRsnG23LHGYNp8n7mWL2Q==",
+ "dependencies": [
+ "bson",
+ "kareem",
+ "mongodb",
+ "mpath",
+ "mquery",
+ "ms",
+ "sift"
+ ]
+ },
+ "mpath@0.9.0": {
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
+ },
+ "mquery@5.0.0": {
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "dependencies": [
+ "debug"
+ ]
+ },
+ "ms@2.1.3": {
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "punycode@2.3.1": {
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
+ },
+ "sift@17.1.3": {
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
+ },
+ "sparse-bitfield@3.0.3": {
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "dependencies": [
+ "memory-pager"
+ ]
+ },
+ "tr46@5.1.1": {
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dependencies": [
+ "punycode"
+ ]
+ },
+ "webidl-conversions@7.0.0": {
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
+ },
+ "whatwg-url@14.2.0": {
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dependencies": [
+ "tr46",
+ "webidl-conversions"
+ ]
+ }
+ },
+ "workspace": {
+ "dependencies": [
+ "jsr:@std/assert@1",
+ "jsr:@std/random@~0.1.4",
+ "npm:mongoose@^8.20.1"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/containers/vpc-mongodb/app/main.ts b/containers/vpc-mongodb/app/main.ts
new file mode 100644
index 0000000..a91b94b
--- /dev/null
+++ b/containers/vpc-mongodb/app/main.ts
@@ -0,0 +1,86 @@
+import mongoose from "mongoose";
+import { randomIntegerBetween } from "@std/random";
+
+const MONGODB_CERT_PATH = "/tmp/mongodb_cert.pem";
+
+const MONGODB_URI = Deno.env.get("MONGODB_URI")
+const MONGODB_CERT = Deno.env.get("MONGODB_CERT")
+
+let mongooseConnected = false;
+
+async function getMongooseConnection() {
+ if (!mongooseConnected) {
+ await mongoose.connect(MONGODB_URI || "", {
+ tls: true,
+ tlsCAFile: MONGODB_CERT_PATH,
+ } as mongoose.ConnectOptions);
+
+ mongooseConnected = true;
+ }
+}
+
+const CHECK_CONNECTION = new URLPattern({ pathname: "/check_connection" });
+const CREATE_PERSON = new URLPattern({ pathname: "/person/:name" });
+const LIST_PEOPLE = new URLPattern({ pathname: "/people" });
+
+async function handler(req: Request): Promise {
+ const url = new URL(req.url);
+
+ if (CHECK_CONNECTION.test(url)) {
+ return await check_connection(req);
+ } else if (CREATE_PERSON.test(url)) {
+ return await create_person(req);
+ } else if (LIST_PEOPLE.test(url)) {
+ return await list_people(req);
+ } else {
+ return new Response("Not Found", { status: 404 });
+ }
+}
+
+const peopleSchema = new mongoose.Schema({ name: String, age: Number, });
+const ExamplePeople = mongoose.model('Example', peopleSchema);
+
+async function create_person(req: Request): Promise {
+ await getMongooseConnection();
+
+ const url = new URL(req.url)
+ const name = url.pathname.split("/person/")[1];
+
+ const examplePerson = new ExamplePeople({
+ name: name,
+ age: randomIntegerBetween(1, 100),
+ });
+ await examplePerson.save();
+
+ return new Response(`Created person: ${examplePerson.name}`, { status: 201 });
+}
+
+async function list_people(_req: Request): Promise {
+ await getMongooseConnection();
+
+ const people = await ExamplePeople.find().exec();
+ const peopleNames = people.map(person => person.name).join(", ");
+
+ return new Response(`People: ${peopleNames}`, { status: 200 });
+}
+
+async function check_connection(_req: Request): Promise {
+ try {
+ await getMongooseConnection();
+
+ const connectionState = mongoose.connection.readyState;
+ const body = `Mongoose connection state: ${connectionState}`;
+
+ return new Response(body, { status: 200 });
+ } catch (error) {
+ return new Response(`MongoDB connection failed: ${error}`, { status: 500 });
+ }
+}
+
+if (import.meta.main) {
+ if (MONGODB_CERT) {
+ await Deno.writeTextFile(MONGODB_CERT_PATH, MONGODB_CERT);
+ }
+
+ Deno.serve({ port: 8080, hostname: "0.0.0.0" }, handler);
+}
diff --git a/containers/vpc-mongodb/main.tf b/containers/vpc-mongodb/main.tf
new file mode 100644
index 0000000..719fb3e
--- /dev/null
+++ b/containers/vpc-mongodb/main.tf
@@ -0,0 +1,132 @@
+locals {
+ name_prefix = "mongodb-example"
+ tags = ["serverless-examples", "mongodb", "terraform"]
+}
+
+resource "scaleway_account_project" "main" {
+ name = "${local.name_prefix}-project"
+}
+
+resource "scaleway_vpc" "main" {
+ project_id = scaleway_account_project.main.id
+
+ name = "${local.name_prefix}-vpc"
+ tags = local.tags
+}
+
+resource "scaleway_vpc_private_network" "main" {
+ project_id = scaleway_account_project.main.id
+ vpc_id = scaleway_vpc.main.id
+
+ name = "${local.name_prefix}-private-network"
+ tags = local.tags
+}
+
+resource "scaleway_mongodb_instance" "main" {
+ project_id = scaleway_account_project.main.id
+
+ name = "${local.name_prefix}-instance"
+ tags = local.tags
+
+ version = "7.0.12"
+
+ node_type = "MGDB-PLAY2-NANO"
+ node_number = 1
+ volume_size_in_gb = 10
+
+ public_network {}
+
+ private_network {
+ pn_id = scaleway_vpc_private_network.main.id
+ }
+
+ user_name = var.mongodb_admin_username
+ password = var.mongodb_admin_password
+
+ is_snapshot_schedule_enabled = false
+}
+
+resource "scaleway_mongodb_user" "app_user" {
+ instance_id = scaleway_mongodb_instance.main.id
+ name = var.mongodb_username
+ password = var.mongodb_password
+
+ roles {
+ role = "read_write"
+ any_database = true
+ }
+}
+
+# Scaleway registry namespace names are unique per-region,
+# so we add a random suffix to avoid name collisions.
+resource "random_string" "unique_namespace_suffix" {
+ length = 4
+ lower = true
+ special = false
+}
+
+resource "scaleway_registry_namespace" "main" {
+ project_id = scaleway_account_project.main.id
+
+ name = "${local.name_prefix}-registry-${random_string.unique_namespace_suffix.result}"
+}
+
+resource "scaleway_container_namespace" "main" {
+ project_id = scaleway_account_project.main.id
+
+ name = "${local.name_prefix}-namespace"
+ description = "Namespace for MongoDB client"
+ tags = local.tags
+}
+
+locals {
+ mongodb_pn = scaleway_mongodb_instance.main.private_network[0]
+ mongodb_private_endpoint = local.mongodb_pn.dns_records[0]
+ mongodb_private_port = local.mongodb_pn.port
+}
+
+// Note: this is somewhat hacky, in a more real world scenario you'd likely want to
+// build and push the Docker image outside of Terraform, e.g. in a CI/CD pipeline.
+resource "null_resource" "build_and_push_docker_image" {
+ depends_on = [scaleway_registry_namespace.main]
+
+ provisioner "local-exec" {
+ command = <