---
title: Deploy a Next.js application using Scaleway Serverless Containers and Serverless SQL Databases
description: This page explains how to deploy your serverless Next.js application using Serverless Containers and Serverless SQL Databases
products:
  - sql-databases
tags: nextjs sql
dates:
  validation: 2025-03-27
  posted: 2024-03-14
  validation_frequency: 12
difficulty: beginner
usecase:
  - application-hosting
ecosystem:
  - third-party
---
import Requirements from '@macros/iam/requirements.mdx'


This tutorial will guide you through deploying a fully serverless Next.js application using a Serverless Container and a Serverless SQL Database.

You can either deploy your application:
  - [step by step using Scaleway CLI](#deploy-next.js-application-frontend-using-serverless-containers) to understand each detailed action performed, and the resources and subresources required
  - [using a Terraform/OpenTofu template](#deploy-a-next.js-application-using-terraform-templates) to deploy your application faster and have a ready-to-use Infrastructure as Code.

<Requirements />

- A Scaleway account logged into the [console](https://console.scaleway.com)
- [Owner](/iam/concepts/#owner) status or [IAM permissions](/iam/concepts/#permission) allowing you to perform actions in the intended Organization
- A valid [API key](/iam/how-to/create-api-keys/)
- Installed and initialized the [Scaleway CLI](/scaleway-cli/quickstart/)
- Installed [Docker Engine](https://docs.docker.com/engine/install/)
- Installed [Node 18 or newer](https://nodejs.org/en/download)

## Deploy Next.js application frontend using Serverless Containers

### Initializing the project

1. Run the command below in a terminal to export your API access and secret keys as environment variables:
    ```bash
    export SCW_ACCESS_KEY=$(scw config get access-key)
    export SCW_SECRET_KEY=$(scw config get secret-key)
    ```

2. Run the command below to make sure the environment variables are properly set:

    ```bash
    scw info
    ```

    This command displays your access key and secret key in the last two lines of the output. The `ORIGIN` column should display `env (SCW_ACCESS_KEY)` and `env (SCW_SECRET_KEY)`, and not `default profile`.

    ```bash
    KEY                      VALUE                                       ORIGIN
    (...)
    access_key               SCWF9DETBF829TAFB1TB                        env (SCW_ACCESS_KEY)
    secret_key               9a1bcf92-xxxx-xxxx-xxxx-xxxxxxxxxxxx        env (SCW_SECRET_KEY)
    ```

3. Access the folder where you want to store your project repository and run the following command to initialize a blog template application:
    ```bash
    npx create-next-app --example blog-starter my-nextjs-blog
    ```

4. Type `y` when prompted to install the dependencies:
    ```bash
    Need to install the following packages:
    create-next-app@14.0.3
    Ok to proceed? (y) y
    ```
    This will create a folder named `my-nextjs-blog`.

5. Access the folder you just created:
    ```bash
    cd my-nextjs-blog
    ```

6. Make sure you can run the application locally:
    ```bash
    npm run dev
    ```

7. Access `http://localhost:3000` in a browser. The blog template displays.

### Creating an image containing your Next.js app

1. Run the following command to create a `Dockerfile`:
    ```bash
    touch Dockerfile
    ```
2. Add the following code to it:
    ```bash
    # syntax=docker/dockerfile:1

    FROM node:20-alpine

    #Built time arguments used for pre-rendering
    ARG PGHOST
    ARG PGPORT
    ARG PGDATABASE
    ARG PGUSER
    ARG PGPASSWORD

    WORKDIR /usr/app
    COPY ./ ./
    RUN npm install
    RUN npm run build

    #Web application configuration
    ENV PORT=8080

    #Database configuration used for dynamically rendered data. These default values should be overwritten by container runtime environment variables
    ENV PGHOST=localhost
    ENV PGPORT=5432
    ENV PGDATABASE=database
    ENV PGUSER=user
    ENV PGPASSWORD=password

    CMD npm run start
    ```

3. Create a `.dockerignore` file by copying the `.gitignore` file:
    ```bash
    cp .gitignore .dockerignore
    ```

    Docker will now ignore node dependencies and build files generated during the container build.

4. Build your application container:
    ```bash
    docker build -t my-nextjs-blog .
    ```

5. Check that your container runs locally:
    ```bash
    docker run -it -p 8080:8080 my-nextjs-blog
    ```

6. Access `http://localhost:8080` in a browser. The blog template displays.

    <Message type="tip">
    When connecting to the webpage, your terminal running Docker might display the following error `Error: ENOENT: no such file or directory, open '/usr/app/_posts/%5Bslug%5D.md'`. This will be fixed in further steps.
    </Message>

### Pushing the image to Scaleway Container Registry

1. Run the following command to create a [Container Registry namespace](/container-registry/concepts/#namespace) and export its endpoint as a variable:
    ```bash
    export REGISTRY_ENDPOINT=$(scw registry namespace create -o json | jq -r '.endpoint')
    ```
2. Log in to your [Container Registry](/container-registry/concepts/#container-registry) from your local terminal:
    ```bash
    docker login $REGISTRY_ENDPOINT -u nologin --password-stdin <<< "$SCW_SECRET_KEY"
    ```
    The following output displays:
    ```bash
    Login Succeeded
    ```

3. Tag and push your container image to your Container Registry namespace:
    ```bash
    docker tag my-nextjs-blog:latest $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    docker push $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    ```

### Deploying the container with Scaleway Serverless Containers

1. Run the following command to create a [Serverless Containers namespace](/serverless-containers/concepts/#namespace) and export its ID as a variable:
    ```bash
    export CONTAINER_NAMESPACE_ID=$(scw container namespace create name="my-nextjs-blog-ns" -o json | jq -r '.id')
    ```

2. Deploy a Serverless Container with your application image:
    ```bash
    scw container container create name="my-nextjs-blog" namespace-id=$CONTAINER_NAMESPACE_ID registry-image=$REGISTRY_ENDPOINT/my-nextjs-blog:latest
    ```

    <Message type="tip">
    The first deployment can take up to two minutes. You can check the deployment status with the following command:
    ```bash
    scw container container list name=my-nextjs-blog
    ```
    When the status appears as `ready`, you can access the website via your browser.
    </Message>

3. Copy the endpoint URL displayed next to the `DomainName` property, and paste it into your browser. The blog template displays.

Your Next.js application frontend is now online.

Your application cannot store any data persistently yet. We will now add serverless storage in the next section.

## Add serverless storage to your application using a Serverless SQL Database

### Setting up the database

1. Run the following command to create a [Serverless SQL Database](/serverless-sql-databases/reference-content/serverless-sql-databases-overview/) and export its attributes as variables:

    ```bash
    export PGHOST=$(scw sdb-sql database create name=tutorial-nextjs-blog-db cpu-min=0 cpu-max=4 -o json | jq -r '.endpoint' | cut -d "/" -f3 | cut -d ":" -f1 )
    export PGPORT='5432'
    export PGDATABASE='tutorial-nextjs-blog-db'
    export PGUSER=$(scw iam api-key get $SCW_ACCESS_KEY -o json | jq -r '.user_id')
    export PGPASSWORD=$SCW_SECRET_KEY
    ```

    These environment variables are default PostgreSQL variables and are automatically recognized by most PostgreSQL-compatible tools.

2. Connect to your database using the `psql` tool:
    ```bash
    psql
    ```
    <Message type="note">
    `psql` automatically uses `PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER`, `PGPASSWORD` environment variables as default arguments.
    </Message>

    An input field with the name of your database displays:

    ```bash
    psql (15.3, server 16.1 (Debian 16.1-1.pgdg120+1))
    SSL connection (protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256, compression: off)
    Type "help" for help.

    tutorial-nextjs=>
    ```

3. Create a table structure with the following query:
    ```sql
    create table posts (title char(100), excerpt text, coverImage text, date date, author_name char(50), author_picture text, ogImage_url text, slug char(50), content text);
    ```

4. Add data using the following command:
   ```sql
   insert into posts values
     ('Learn How to Pre-render Pages Using Static Generation with Next.js',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/hello-world/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'Tim Neutkens',
     '/assets/blog/authors/tim.jpeg',
     '/assets/blog/hello-world/cover.jpg',
     'hello-world',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.'),
     ('Dynamic Routing and Static Generation',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/dynamic-routing/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'JJ Kasper',
     '/assets/blog/authors/jj.jpeg',
     '/assets/blog/dynamic-routing/cover.jpg',
     'dynamic-routing',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.'),
     ('Preview Mode for Static Generation',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/preview/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'Joe Haddad',
     '/assets/blog/authors/joe.jpeg',
     '/assets/blog/preview/cover.jpg',
     'preview',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.');
   ```

5. Exit the database query interface with the following command:
    ```bash
    exit
    ```

6. Run the command below to add the `node-postgres` dependency to your application:
    ```bash
    npm install pg && npm install --save-dev @types/pg
    ```

    This library is required to connect from your front end to your database.

Your application folder should be called `my-nextjs-blog` and structured as follows:
  ```bash
  my-nextjs-blog/
      .next/
      app/
      node_modules/
      public/
      pages/
      src/
      package.json
      tsconfig.json
  ```

### Integrating the database into your application

You need to edit 3 files to have a proper integration with your database.

<Message type="tip">
If you want to avoid doing these modifications manually, you can also retrieve the final code from this tutorial from our [serverless-examples](https://github.com/scaleway/serverless-examples/tree/main/projects/tutorial-sdb-nextjs-terraform) repository with the command below:
```
git clone git@github.com:scaleway/serverless-examples.git
```
Note that this repository differs slightly in configuration, as it will dynamically render pages and not pre-render them in the Next.js app build phase.
</Message>

1. Edit the file located at `src/lib/api.ts` by replacing its content with the following code. This will switch frontend content from local data to your **Serverless SQL Database**.
    ```typescript
    import { Post } from "@/interfaces/post";
    import fs from "fs";
    import matter from "gray-matter";
    import { join } from "path";
    import { Client } from 'pg'


    export type Row = {
      slug: string;
      title: string;
      date: Date;
      coverimage: string;
      author_name: string;
      author_picture: string;
      excerpt: string;
      ogimage_url: string;
      content: string;
      preview?: boolean;
    };

    const client = new Client({
        ssl: {
          rejectUnauthorized: false,
        }
      })

    const connect = async () => {
      await client.connect()
    }

    connect()

    //This default function handles Nextjs pre-rendering with undefined values
    export function getPostFromRow(row: Row){
      if (row === undefined){
        var postRow: Row = {
          slug: "Title",
          title: "Excerpt",
          date: new Date("2024-01-16"),
          coverimage: "",
          author_name: "",
          author_picture: "",
          excerpt: "",
          ogimage_url: "",
          content: "",
          preview: false
        }
      } else{
        var postRow = row
      }

      const post = {
        title: postRow.title,
        excerpt: postRow.excerpt,
        coverImage: postRow.coverimage,
        date: postRow.date.toISOString(),
        author: {
          name: postRow.author_name,
          picture: postRow.author_picture
        },
        ogImage: {
          url: postRow.ogimage_url
        },
        slug: postRow.slug,
        content: postRow.content,
        preview: false
      }
      return post;
    }


    export async function getPostBySlug(slug: string): Promise<Post> {
      const data = await client.query('SELECT * FROM posts WHERE slug=$1;',[slug])
      const post = getPostFromRow(data.rows[0])
      return post;
    }


    export async function getAllPosts(): Promise<Post[]> {
      const data = await client.query('SELECT * FROM posts;')
      const posts = data.rows.map((row) => (getPostFromRow(row)))
      return posts;
    }
    ```

2. Edit the file located at `src/app/page.tsx` by replacing its content with the following code. This will update the main application home page, which displays the posts list.
    ```typescript
    import Container from "@/app/_components/container";
    import { HeroPost } from "@/app/_components/hero-post";
    import { Intro } from "@/app/_components/intro";
    import { MoreStories } from "@/app/_components/more-stories";
    import { getAllPosts } from "../lib/api";

    export default async function Index() {
      const allPosts = await getAllPosts();

      const heroPost = allPosts[0];

      const morePosts = allPosts.slice(1);

      return (
        <main>
          <Container>
            <Intro />
            <HeroPost
              title={heroPost.title}
              coverImage={heroPost.coverImage}
              date={heroPost.date}
              author={heroPost.author}
              slug={heroPost.slug}
              excerpt={heroPost.excerpt}
            />
            {morePosts.length > 0 && <MoreStories posts={morePosts} />}
          </Container>
        </main>
      );
    }
    ```

3. Edit the file located at `src/app/posts/[slug]/page.tsx` by replacing its content with the following code. This will update pages that display a single post.
    ```typescript
    import { Metadata } from "next";
    import { notFound } from "next/navigation";
    import { getAllPosts, getPostBySlug } from "../../../lib/api";
    import { CMS_NAME } from "../../../lib/constants";
    import markdownToHtml from "../../../lib/markdownToHtml";
    import Alert from "../../_components/alert";
    import Container from "../../_components/container";
    import Header from "../../_components/header";
    import { PostBody } from "../../_components/post-body";
    import { PostHeader } from "../../_components/post-header";

    export default async function Post({ params }: Params) {
      const post = await getPostBySlug(params.slug)

      if (!post) {
        return notFound();
      }

      return (
        <main>
          <Alert preview={post.preview} />
          <Container>
            <Header />
            <article className="mb-32">
              <PostHeader
                title={post.title}
                coverImage={post.coverImage}
                date={post.date}
                author={post.author}
              />
              <PostBody content={post.content} />
            </article>
          </Container>
        </main>
      );
    }

    type Params = {
      params: {
        slug: string;
      };
    };

    export async function generateMetadata({ params }: Params): Promise<Metadata> {
      const post = await getPostBySlug(params.slug);

      if (!post) {
        return notFound();
      }

      const title = `${post.title} | Next.js Blog Example with ${CMS_NAME}`;

      return {
        openGraph: {
          title,
          images: [post.ogImage.url],
        },
      };
    }

    export async function generateStaticParams() {
      const posts = await getAllPosts();

      return posts.map((post) => ({
        slug: post.slug,
      }));
    }
    ```

4. Run the following command to ensure that you can run the application locally:
    ```bash
    npm run dev
    ```

5. Access `http://localhost:3000` in a browser. The images and titles of the first blog post have been updated with the content from your database.

Congratulations! You can already deploy your application by building a new container version, pushing it to the **Container Registry**, and deploying it to **Serverless Containers**.

However, your application would then connect with your [user credentials](/iam/concepts/#user), which is not a recommended security practice.
To secure your deployment, we will now add a dedicated [IAM application](/iam/concepts/#application), give it the minimum required permissions, and provide its credentials to your application.

## Secure and redeploy your application

1. Run the following command to create an [IAM application](/iam/concepts/#application) and export it as a variable:
    ```bash
    export SCW_APPLICATION_ID=$(scw iam application create name=tutorial-nextjs-app -o json | jq -r '.id')
    ```
    The `SCW_APPLICATION_ID` environment variable will store the IAM application ID so you can use it in later commands.

2. Create an [IAM policy](/iam/concepts/#policy) giving your application rights to access the database:
    ```bash
    scw iam policy create application-id=$SCW_APPLICATION_ID name=tutorial-nextjs-policy rules.0.organization-id=$(scw config get default-organization-id) rules.0.permission-set-names.0=ServerlessSQLDatabaseFullAccess
    ```
    The permission `ServerlessSQLDatabaseFullAccess` lets your application read and write data or create new databases. You can restrict this later to fit your database use cases, using `ServerlessSQLDatabaseRead` or `ServerlessSQLDatabaseReadWrite` permission sets.

3. Create an [IAM API Key](/iam/concepts/#api-key) and export its secret key as an environment variable:
    ```bash
    export SCW_APPLICATION_SECRET=$(scw iam api-key create application-id=$SCW_APPLICATION_ID -o json | jq -r '.secret_key')
    ```

4. Build your application container with the updated code:
    ```bash
    docker build -t my-nextjs-blog --build-arg PGHOST --build-arg PGDATABASE --build-arg PGPORT --build-arg PGUSER=$SCW_APPLICATION_ID --build-arg PGPASSWORD=$SCW_APPLICATION_SECRET .
    ```
    This time, the `build` command requires environment variables, because Next.js pre-rendering will access your database while building time to load page content.

    You can check that your container is running correctly locally with:
    ```bash
    docker run -it -p 8080:8080 -e PGHOST -e PGDATABASE -e PGPORT -e PGUSER=$SCW_APPLICATION_ID -e PGPASSWORD=$SCW_APPLICATION_SECRET my-nextjs-blog
    ```

5. Access http://localhost:8080 in a browser. The blog template displays.

6. Push your containerized application to the Container Registry:
    ```bash
    docker tag my-nextjs-blog:latest $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    docker push $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    ```

7. Retrieve your container's ID:
    ```bash
    export CONTAINER_ID=$(scw container container list name="my-nextjs-blog" -o json | jq -r '.[0].id')
    ```

8. Redeploy your Serverless Container:
    ```bash
    scw container container deploy $CONTAINER_ID
    ```

9. Refresh your browser page displaying the blog. An updated version displays.

  Congratulations, you have deployed a full serverless Next.js application!

## Going further with containers

  - **Edit the source code** locally, apply `scw container deploy` again, and see the **new version go live** in seconds.

- Inspect your newly created resources in the Scaleway console:

  - You can display your **Registry namespace** and **container image** in the [Container Registry section](https://console.scaleway.com/registry/namespaces).

  - You can display your **Serverless Containers namespace** and **container deployment** in the [Serverless Containers section](https://console.scaleway.com/containers/namespaces).

  - You can display your **Serverless SQL Database** in the [Serverless SQL Databases section](https://console.scaleway.com/serverless-db/databases).

- **Fine-tune deployment options** such as autoscaling, targeted regions, and more. You can find more information by typing `scw container deploy --help` in your terminal, or by referring to the [dedicated documentation](/serverless-containers/how-to/manage-a-container/).

## Deploy a Next.js application using Terraform/OpenTofu templates

<Message type="note">
  You must have [Terraform/OpenTofu](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) installed and configured with [Scaleway Terraform](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs) or [OpenTofu](https://search.opentofu.org/provider/opentofu/scaleway/latest) Provider for this part.
</Message>

### Initialize the project

1. Run the command below to export your access key and secret key as environment variables:
    ```bash
    export SCW_ACCESS_KEY=$(scw config get access-key)
    export SCW_SECRET_KEY=$(scw config get secret-key)
    ```

2. Run the command below to make sure the environment variables are properly set:

    ```bash
    scw info
    ```

    This command displays your access_key and secret_key in the last two lines of the output. The `ORIGIN` column should display `env (SCW_ACCESS_KEY)` and `env (SCW_SECRET_KEY)`, and not `default profile`.

    ```bash
    KEY                      VALUE                                       ORIGIN
    (...)
    access_key               SCWF9DETBF829TAFB1TB                        env (SCW_ACCESS_KEY)
    secret_key               9a1bcf92-xxxx-xxxx-xxxx-xxxxxxxxxxxx        env (SCW_SECRET_KEY)
    ```

3. Run the following command to create a folder to store the template source code, and access it:
    ```bash
    mkdir my-nextjs-blog
    && cd my-nextjs-blog
    ```
4. Copy the files from the [serverless-examples](https://github.com/scaleway/serverless-examples/tree/main/projects/tutorial-sdb-nextjs-terraform) repository to the folder.
      <Message type="note">
        These files extend the blog demo application from `npx create-next-app --example blog-starter my-nextjs-blog`, and add the required code to fetch data from a database instead of static markdown files.
      </Message>
5. Install the required dependencies:
    ```bash
    npm install
    ```
6. Confirm you can run the application locally with the following commands:
    ```bash
    npm run dev
    ```
7. Access http://localhost:3000 in a browser. The blog template displays.

### Building the image containing your Next.js app

1. Build your application container with:
    ```bash
    docker build -t my-nextjs-blog .
    ```
    <Message type="tip">
      An error message `Error: connect ECONNREFUSED` might display when building. This is linked to Next.js pre-rendering stage without any database to connect to yet but will raise any issue at runtime because all pages will be dynamically rendered.
    </Message>

2. Run the following command to check that your container runs locally:
    ```bash
    docker run -it -p 8080:8080 my-nextjs-blog
    ```
3. Go to [localhots:8080](http://localhost:8080) in your browser. The message below displays because your application is not connected to the database yet.
    ```
    Application error: a server-side exception has occurred (see the server logs for more information)
    ```

### Pushing the image containing your Next.js app

1. Run the following command to create a [Container Registry namespace](/container-registry/concepts/#namespace) and export its endpoint as a variable:
    ```bash
    export REGISTRY_ENDPOINT=$(scw registry namespace create -o json | jq -r '.endpoint')
    ```

2. Log in to your [Container Registry](/container-registry/concepts/#container-registry) from your local terminal:
    ```bash
    docker login $REGISTRY_ENDPOINT -u nologin --password-stdin <<< "$SCW_SECRET_KEY"
    ```
    The following output displays:
    ```bash
    Login Succeeded
    ```

3. Tag and push your container image to your Container Registry namespace:
    ```bash
    docker tag my-nextjs-blog:latest $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    docker push $REGISTRY_ENDPOINT/my-nextjs-blog:latest
      ```

### Creating the Terraform/OpenTofu configuration

1. Run the following command to create a new folder inside `my-nextjs-blog` to store your Terraform/OpenTofu files, and access it:
    ```bash
    cd ..
    mkdir terraform-nextjs-blog
    && cd terraform-nextjs-blog
    ```

2. Create an empty `main.tf` Terraform/OpenTofu file inside the folder.

    Your application folder should now be structured as follows:
    ```bash
    my-nextjs-blog/
    terraform-nextjs-blog/
        main.tf
    ```
3. Add the following code to your `main.tf` file:
    ```json
    terraform {
    required_providers {
      scaleway = {
        source = "scaleway/scaleway"
      }
    }
    required_version = ">= 0.13"
    }

    variable "REGISTRY_ENDPOINT" {
    type = string
    description = "Container Registry endpoint where your application container is stored"
    }

    variable "DEFAULT_PROJECT_ID" {
    type = string
    description = "Project id where your resources will be created"
    }

    resource scaleway_container_namespace main {
      name = "tutorial-nextjs-blog-tf"
      description = "Namespace created for full serverless Nextjs app deployment"
    }

    resource scaleway_container main {
      name = "tutorial-nextjs-blog-tf"
      description = "Container for Nextjs blog"
      namespace_id = scaleway_container_namespace.main.id
      registry_image = "${var.REGISTRY_ENDPOINT}/my-nextjs-blog:latest"
      port = 8080
      cpu_limit = 560
      memory_limit = 1024
      min_scale = 0
      max_scale = 5
      timeout = 600
      max_concurrency = 80
      privacy = "public"
      protocol = "http1"
      deploy = true

      environment_variables = {
          "PGUSER" = scaleway_iam_application.app.id,
          "PGHOST" = trimsuffix(trimprefix(regex(":\\/\\/.*:",scaleway_sdb_sql_database.database.endpoint), "://"),":")
          "PGDATABASE" = scaleway_sdb_sql_database.database.name,
          "PGPORT" = trimprefix(regex(":[0-9]{1,5}",scaleway_sdb_sql_database.database.endpoint), ":")
      }
      secret_environment_variables = {
        "PGPASSWORD" = scaleway_iam_api_key.api_key.secret_key,
      }
    }

    resource scaleway_iam_application "app" {
    name = "tutorial-nextjs-app-tf"
    }

    resource scaleway_iam_policy "db_access" {
    name = "tutorial-nextjs-policy-tf"
    description = "Gives tutorial Nextjs app access to Serverless SQL Database"
    application_id = scaleway_iam_application.app.id
    rule {
      project_ids = ["${var.DEFAULT_PROJECT_ID}"]
      permission_set_names = ["ServerlessSQLDatabaseReadWrite"]
    }
    }

    resource scaleway_iam_api_key "api_key" {
    application_id = scaleway_iam_application.app.id
    }

    resource scaleway_sdb_sql_database "database" {
    name = "tutorial-nextjs-tf"
    min_cpu = 0
    max_cpu = 8
    }

    output "database_connection_string" {
    // Output as an example, you can give this string to your application
    value = format("postgres://%s:%s@%s",
      scaleway_iam_application.app.id,
      scaleway_iam_api_key.api_key.secret_key,
      trimprefix(scaleway_sdb_sql_database.database.endpoint, "postgres://"),
    )
    sensitive = true
    }

    output "container_url" {
    // Output as an example, you can give this string to your application
    value = scaleway_container.main.domain_name
    sensitive = true
    }
    ```

The Terraform/OpenTofu file creates several resources:

- A [Serverless Containers namespace](/serverless-containers/concepts/#namespace), that contains a [Serverless Container](/serverless-containers/quickstart/), which hosts your Next.js application

- A [Serverless SQL Database](/serverless-sql-databases/reference-content/serverless-sql-databases-overview/) which stores posts data

- An [IAM policy](/iam/concepts/#policy) which grants your application the right permissions

- An [IAM application](/iam/concepts/#application), used as a principal for your IAM policy

- An [IAM API key](/iam/concepts/#api-key) used as credentials to authenticate your application to the database

### Deploying the app with Terraform/OpenTofu

1. Run the following command to initialize the Terraform/OpenTofu working directory:
    ```bash
    terraform init
    ```

2. Add the `REGISTRY_ENDPOINT` and `DEFAULT_PROJECT_ID` environment variables to Terraform/OpenTofu:
    ```bash
    export TF_VAR_REGISTRY_ENDPOINT=$REGISTRY_ENDPOINT
    export TF_VAR_DEFAULT_PROJECT_ID=$(scw config get default-project-id)
    ```

3. Create and review the Terraform/OpenTofu execution plan of your infrastructure:
    ```bash
    terraform plan
    ```

4. Deploy your application by executing the actions listed in your Terraform/OpenTofu plan:
    ```bash
    terraform apply
    ```

    The output will provide the URL with which you can access your application and the connection string for your database. Since they contain sensitive values, they are hidden by default.

5. Run the following command to display them:
    ```
    terraform output -json
    ```

    A similar output displays:

    ```json
    {
     "container_url": {
       "sensitive": true,
       "type": "string",
       "value": "tutorialnextjsblogtfaxtypxrf-tutorial-nextjs-blog-tf.functions.fnc.fr-par.scw.cloud"
     },
     "database_connection_string": {
       "sensitive": true,
       "type": "string",
       "value": "postgres://example-f6b7-40cc-9ae8-7f24e64c6531:example-c770-46ea-b785-94bf39536e6a@650c9680-1100-48e4-b5a6-ff2ff5bcf142.pg.sdb.fr-par.scw.cloud:5432/tutorial-nextjs-tf?sslmode=require"
     }
    }
    ```

6. Copy the container URL from the output and paste it into a browser to access your application. However, no post will be displayed because your database is still empty.

### Adding content to the Serverless SQL Database

1. Run the following command to connect to your database using the link provided by Terraform/OpenTofu's output:
    ```sql
    psql $(terraform output -json | jq -r '.database_connection_string.value')
    ```
    An input field with the name of your database displays:
    ```sql
    psql (15.3, server 16.1 (Debian 16.1-1.pgdg120+1))
    SSL connection (protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256, compression: off)
    Type "help" for help.

    tutorial-nextjs=>
    ```
2. Create a table structure with the following query:
    ```sql
    create table posts (title char(100), excerpt text, coverImage text, date date, author_name char(50), author_picture text, ogImage_url text, slug char(50), content text);
    ```

3. Add data using the following command:
   ```sql
   insert into posts values
     ('Learn How to Pre-render Pages Using Static Generation with Next.js',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/hello-world/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'Tim Neutkens',
     '/assets/blog/authors/tim.jpeg',
     '/assets/blog/hello-world/cover.jpg',
     'hello-world',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.'),
     ('Dynamic Routing and Static Generation',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/dynamic-routing/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'JJ Kasper',
     '/assets/blog/authors/jj.jpeg',
     '/assets/blog/dynamic-routing/cover.jpg',
     'dynamic-routing',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.'),
     ('Preview Mode for Static Generation',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus.',
     '/assets/blog/preview/cover.jpg',
     '2020-03-16T05:35:07.322Z',
     'Joe Haddad',
     '/assets/blog/authors/joe.jpeg',
     '/assets/blog/preview/cover.jpg',
     'preview',
     'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies.');
   ```

4. Exit the database query interface with the following command:
    ```sql
    exit
    ```

5. Run the command below again, and open the container URL in a browser to access your application.
    ```bash
    terraform output -json
    ```

    Your serverless application is now online.

6. Once you are done, run the following command to stop all your resources:
    ```hcl
    terraform destroy
    ```

### Going further

- **Edit the source code** locally, then:
  - Build a new container version with the following command:

      ```bash
      docker build -t my-nextjs-blog .
      ```

  - Push the new container to **Container Registry**:

    ```bash
    docker tag my-nextjs-blog:latest $REGISTRY_ENDPOINT/my-nextjs-blog:latest
    docker push $REGISTRY_ENDPOINT/my-nextjs-blog:latest`
    ```

  - Update and apply a new container deployment:

    ```
    export CONTAINER_ID=$(scw container container list name="my-nextjs-blog" -o json | jq -r '.[0].id')
    scw container container deploy $CONTAINER_ID
    ```

  - The new version is live a few seconds later.

- Inspect your newly created resources in the Scaleway console:

  - You can display your **Registry namespace** and **container image** in the [Container Registry section](https://console.scaleway.com/registry/namespaces)

  - You can display your **Serverless Containers namespace** and **container deployment** in the [Serverless Containers section](https://console.scaleway.com/containers/namespaces)

  - You can display your **Serverless SQL Database** in the [Serverless SQL Databases section](https://console.scaleway.com/serverless-db/databases)

- **Fine-tune deployment options** such as autoscaling, targeted regions, and more. You can find more information by typing `scw container deploy --help` in your terminal, or by referring to the [dedicated documentation](/serverless-containers/how-to/manage-a-container/).

- **Change the security configuration** for your container. It is currently public, and anyone with the link can access it, but you can make it [private to require authentication](https://www.scaleway.com/en/developers/api/serverless-containers/#authentication).

## Troubleshooting

If you encounter any issues, first check that you meet all the requirements.

- You have installed and configured the [Scaleway CLI](/scaleway-cli/quickstart/). Running the `scw account project get` command should return the following output:
    ```
    ID              example-7afa-4a5d-9f6c-27db072f1527
    Name            default
    OrganizationID  example-7afa-4a5d-9f6c-27db072f1527
    CreatedAt       1 year ago
    UpdatedAt       1 year ago
    Description     -
    ```
  <Message type="tip">
  You can also find and compare your Project and Organization ID in the [Scaleway console settings](https://console.scaleway.com/project/settings).
  </Message>
- You have **Docker Engine** installed. Running the `docker -v` command in a terminal should display your currently installed Docker version:
    ```
    Docker version 24.0.6, build ed223bc820
    ```
- You have the right [IAM permissions](/iam/reference-content/policy/), specifically **ContainersRegistryFullAccess**, **ContainersFullAccess** and **ServerlessSQLDatabaseFullAccess**.