> ## Documentation Index
> Fetch the complete documentation index at: https://www.qovery.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use AWS IAM Roles with Qovery

> Grant AWS IAM permissions to applications without managing credentials

This tutorial demonstrates how to grant AWS IAM permissions to applications, containers, or jobs deployed through Qovery, using IRSA (IAM Roles for Service Accounts). This approach eliminates credential management by automatically rotating tokens through AWS's identity service.

Everything deploys through Qovery services from the [create\_service\_account](https://github.com/Qovery/create_service_account) repository:

1. A **Terraform service** creates the IAM role (and the OIDC identity provider if needed)
2. A **Helm service** creates the Kubernetes ServiceAccount annotated with the role ARN
3. Your application picks up the ServiceAccount through the **Service account** advanced setting

The Terraform outputs flow into the Helm values automatically, so nothing is hardcoded. If you prefer to create the IAM resources by hand, the manual AWS Console path is kept as an alternative in step 2.

## Prerequisites

* A Qovery cluster running on AWS EKS
* Basic knowledge of AWS IAM and Kubernetes
* AWS credentials allowed to manage IAM (for the Terraform service or the Console)

## Step 1: Create an Application Requiring S3 Permissions

First, deploy a simple Debian container that will need S3 access:

* Deploy a Debian container with 1 instance and 128MB memory
* You can also use existing applications, containers, or jobs

### Get Kubernetes Namespace Name

1. Access your container variables and locate `QOVERY_KUBERNETES_NAMESPACE_NAME`
2. This value represents the namespace where the container runs

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/debian_namespace.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=47b5e46984f7b3a1aa6093ad51f35ee7" alt="Get Kubernetes namespace from container variables" width="2784" height="1830" data-path="images/aws-iam-assume-role/debian_namespace.png" />
</Frame>

***

## Step 2: Create the OIDC Provider and IAM Role

<Tabs>
  <Tab title="Terraform service (recommended)">
    Add a **Terraform service** to the same environment:

    * Source: Git repository `https://github.com/Qovery/create_service_account`
    * Root path: `terraform/`

    Set these variables on the service:

    | Variable                                                     | Value                                                                                                                                              |
    | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `TF_VAR_cluster_name`                                        | Alias of the built-in `QOVERY_KUBERNETES_CLUSTER_NAME`                                                                                             |
    | `TF_VAR_role_name`                                           | Name for the IAM role, e.g. `my-s3-role`                                                                                                           |
    | `TF_VAR_service_account_name`                                | ServiceAccount name, e.g. `my-app-sa`                                                                                                              |
    | `TF_VAR_policy_arns`                                         | e.g. `["arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"]`                                                                                          |
    | `TF_VAR_namespace`                                           | Alias of `QOVERY_KUBERNETES_NAMESPACE_NAME` to scope the role to this environment, or empty for a cluster-scoped role (required for Preview/Clone) |
    | `TF_VAR_create_oidc_provider`                                | `true` the first time, if the cluster's OIDC provider is not registered in IAM yet                                                                 |
    | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_REGION` | Credentials allowed to manage IAM (mark the secret as such)                                                                                        |

    Deploy the service. The module creates the trust policy for you (namespace-scoped `StringEquals`, or cluster-scoped `StringLike` on `system:serviceaccount:z*:<service_account_name>` when `TF_VAR_namespace` is empty).

    The module outputs `role_arn`. Qovery exposes it to the environment as a variable named `QOVERY_OUTPUT_TERRAFORM_<service short id>_ROLE_ARN`; check the exact name in your environment variables after the first deployment. You will reference it in step 3.
  </Tab>

  <Tab title="Manually (AWS Console)">
    ### Get Cluster OIDC Provider URL

    1. Navigate to your AWS EKS cluster Overview section
    2. Copy the OpenID Connect provider URL

    <Frame>
      <img src="https://mintcdn.com/qovery/8nBiaMjqzLDdT7sp/images/aws-iam-assume-role/eks_oidc.png?fit=max&auto=format&n=8nBiaMjqzLDdT7sp&q=85&s=bc32d3c8fca814ad709da3e8356561f2" alt="EKS OIDC provider URL" width="907" height="469" data-path="images/aws-iam-assume-role/eks_oidc.png" />
    </Frame>

    ### Create Identity Provider

    1. In AWS IAM, access the **Identity providers** section
    2. Select **OpenID Connect** provider type
    3. Paste the provider URL
    4. Click **Get thumbprint**
    5. Add `sts.amazonaws.com` as **Audience**
    6. Create the provider

    <Frame>
      <img src="https://mintcdn.com/qovery/8nBiaMjqzLDdT7sp/images/aws-iam-assume-role/oidc_connect.png?fit=max&auto=format&n=8nBiaMjqzLDdT7sp&q=85&s=8a9f25164a700c929e1f889edf394272" alt="Create OIDC identity provider" width="837" height="1105" data-path="images/aws-iam-assume-role/oidc_connect.png" />
    </Frame>

    ### Create a Role

    1. In IAM Roles section, select **Create role**
    2. Choose **Web identity** as Trusted entity type
    3. Set **Identity provider** and **Audience** to `sts.amazonaws.com`

    <Frame>
      <img src="https://mintcdn.com/qovery/8nBiaMjqzLDdT7sp/images/aws-iam-assume-role/role_create_step1.png?fit=max&auto=format&n=8nBiaMjqzLDdT7sp&q=85&s=61a6e1d36600c18e30f175fb84cd3f61" alt="Create IAM role step 1" width="808" height="731" data-path="images/aws-iam-assume-role/role_create_step1.png" />
    </Frame>

    ### Add Role Permissions

    1. Select the desired policy (example uses `AmazonS3ReadOnlyAccess`)
    2. Set role name and description
    3. Proceed to creation

    ### Configure Trusted Entities

    For a role scoped to one environment, update the trust policy from:

    ```json theme={null}
    "oidc.eks.eu-west-3.amazonaws.com/id/xxxxxxx:aud": "sts.amazonaws.com"
    ```

    To:

    ```json theme={null}
    "oidc.eks.eu-west-3.amazonaws.com/id/xxxxxxx:sub":
    "system:serviceaccount:kubernetes_namespace:service_account_name"
    ```

    Replace placeholders:

    * `kubernetes_namespace`: Your Qovery environment namespace
    * `service_account_name`: Define a service account name (e.g., `my-app-sa`)

    For a cluster-scoped role (required by On-demand/Clone features), use `StringLike` with a wildcard:

    ```json theme={null}
    "Condition": {
        "StringLike": {
            "oidc.eks.eu-west-3.amazonaws.com/id/xxxxxxx:sub":
            "system:serviceaccount:z*:service_account_name"
        }
    }
    ```

    This allows the service account to be used across all namespaces starting with `z`.

    <Frame>
      <img src="https://mintcdn.com/qovery/8nBiaMjqzLDdT7sp/images/aws-iam-assume-role/role_trusted_entities_default.png?fit=max&auto=format&n=8nBiaMjqzLDdT7sp&q=85&s=bb77022fd74bd62c84b0716a40eb0748" alt="Configure trusted entities" width="1088" height="760" data-path="images/aws-iam-assume-role/role_trusted_entities_default.png" />
    </Frame>

    Save the **role ARN** for step 3.
  </Tab>
</Tabs>

***

## Step 3: Create the Service Account

A Helm chart creates the ServiceAccount annotated for IRSA:

```yaml theme={null}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: <service_account_name>
  annotations:
    eks.amazonaws.com/role-arn: <role_arn>
```

Add a **Helm service** to the same environment:

**Repository Details:**

1. Repository name: `Qovery Service Account Helper`
2. Kind: `HTTPS`
3. URL: `https://qovery.github.io/create_service_account/`

**Chart Configuration:**

* Helm source: Helm repository
* Chart name: `qovery-sa-helper`
* Version: `0.2.0`

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/create_sa.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=47b9c607e5dc090a7badefe0a4b1b1aa" alt="Create service account" width="2784" height="1830" data-path="images/aws-iam-assume-role/create_sa.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/helm_sa_1.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=7f907415903e18795b2f2759d42fe969" alt="Helm service configuration" width="2784" height="1830" data-path="images/aws-iam-assume-role/helm_sa_1.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/set-helm-repo.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=827189d14711a5c5d97cfa433bad600c" alt="Set Helm repository" width="2784" height="1830" data-path="images/aws-iam-assume-role/set-helm-repo.png" />
</Frame>

**Values override:**

<Tabs>
  <Tab title="With the Terraform service">
    Reference the Terraform output with the `qovery.env` macro, so the role ARN is never hardcoded:

    ```yaml theme={null}
    serviceAccount:
      name: my-app-sa   # same as TF_VAR_service_account_name
    awsRoleArn: qovery.env.QOVERY_OUTPUT_TERRAFORM_<SHORT_ID>_ROLE_ARN
    ```
  </Tab>

  <Tab title="With a manually created role">
    ```yaml theme={null}
    serviceAccount:
      name: my-app-sa
    awsRoleArn: arn:aws:iam::xxxxxx:role/my-s3-role
    ```
  </Tab>
</Tabs>

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/helm_sa_2.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=0a9245c4fc3a6b5d17c63f6d36912cd6" alt="Override service account name" width="2784" height="1830" data-path="images/aws-iam-assume-role/helm_sa_2.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/helm_sa_3.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=459377fae26e57314987ddcada7b6c61" alt="Override AWS role ARN" width="3164" height="2070" data-path="images/aws-iam-assume-role/helm_sa_3.png" />
</Frame>

Deploy the Helm service and verify it completes successfully:

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/helm_sa_logs.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=02d3441061460f21c45c8406de2b7afc" alt="Service account creation logs" width="2784" height="1830" data-path="images/aws-iam-assume-role/helm_sa_logs.png" />
</Frame>

***

## Step 4: Set Application Service Account

### Configure Service Account

1. Access your application **Advanced settings**
2. Set **Service account** (`security.service_account_name`) to the created service account name
3. Deploy using the **Deploy now** button

<Frame>
  <img src="https://mintcdn.com/qovery/9CNZQIdUELQe9KKR/images/aws-iam-assume-role/debian_sa.png?fit=max&auto=format&n=9CNZQIdUELQe9KKR&q=85&s=68fbd70d19e4bda9e768e4ac79cb019a" alt="Set service account on application" width="2784" height="1830" data-path="images/aws-iam-assume-role/debian_sa.png" />
</Frame>

### Validate Access

Using Qovery CLI:

```bash theme={null}
$ qovery shell
```

Check AWS environment variables:

```bash theme={null}
$ env | grep AWS
AWS_DEFAULT_REGION=us-east-2
AWS_REGION=us-east-2
AWS_ROLE_ARN=arn:aws:iam::xxxxxx:role/my-s3-role
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
AWS_STS_REGIONAL_ENDPOINTS=regional
```

Validate S3 access:

```bash theme={null}
$ apt-get update && apt-get -y install awscli
$ aws s3 ls
```

***

## Key Concepts

* **OIDC Integration**: Enables Kubernetes service accounts to assume AWS roles
* **Token Rotation**: Automatic credential rotation without manual management
* **Namespace Scoping**: Restricts role access to specific Kubernetes namespaces
* **Everything as Qovery services**: The IAM role and the ServiceAccount are regular services in your environment, so they are versioned, visible in the Console, replicated with Clone/Preview, and cleaned up with the environment

## Conclusion

With the Terraform and Helm services in place, granting a role to a new application comes down to setting one advanced setting. The IAM side lives in your environment like any other service, without credential management overhead.

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Terraform Services" icon="https://mintcdn.com/qovery/Nvnl0g5BHzA0XQmy/images/logos/terraform-icon.svg?fit=max&auto=format&n=Nvnl0g5BHzA0XQmy&q=85&s=a0e5acfcbe26b4b86d136930f5b22a57" href="/docs/configuration/terraform" width="24" height="24" data-path="images/logos/terraform-icon.svg">
    Deploy Terraform manifests with Qovery
  </Card>

  <Card title="Advanced Settings" icon="sliders" href="/docs/configuration/service-advanced-settings">
    Configure service advanced settings
  </Card>

  <Card title="Environment Variables" icon="key" href="/docs/configuration/environment-variables">
    Manage environment variables and secrets
  </Card>

  <Card title="Helm Services" icon="https://mintcdn.com/qovery/Nvnl0g5BHzA0XQmy/images/logos/helm-icon.svg?fit=max&auto=format&n=Nvnl0g5BHzA0XQmy&q=85&s=f6c259d3ee3123f80e74bcb99c9f6f1d" href="/docs/configuration/helm" width="24" height="24" data-path="images/logos/helm-icon.svg">
    Deploy applications with Helm
  </Card>
</CardGroup>
