docs
Misc.
Add New to .env

Adding New Variables to .env File

You will need to add new secrets or variables to your .env file. Here’s a simple guide to walk you through the process.

Managing Environment Variables

Handling environment variables is crucial for configuring your project across different stages, from local development to deployment in the cloud. In this guide, we will cover three types of .env files commonly used in our project:

  1. Root .env for local development.
  2. CDK .env for initial deployment with AWS CDK (staging, production).
  3. CI/CD env section for handling secrets securely during automated deployments (staging, production).

We’ll explore their use cases, configurations, and best practices for secure handling.

1. Root .env – For Local Development

The root .env file is used exclusively for local development. It contains environment-specific variables like API keys, database connection strings, or any other configuration needed to run the app locally.

Usage in config.ts

For example, if you want to add a new variable OAUTH_API_KEY to your .env file and test it locally first, you'll need to include it in your root .env. After adding it, make sure to access the variable by including it in the configuration file (src/config/config.ts).

Here's an example using Joi for validation:

.when(Joi.object({ NODE_ENV: Joi.string().valid('development') }).unknown(), {
  OAUTH_API_KEY: Joi.string().required().description('OAUTH_API_KEY'),
})

Then add it to the interface

interface Config {
  ...
  OAUTH_API_KEY: string;
}

Finally, in const config

const config: Config = {
  OAUTH_API_KEY: envVars.OAUTH_API_KEY,
}

By doing this, you ensure that the required environment variables are in place before the app starts, reducing potential runtime errors. You can now use OAUTH_API_KEY as config.OAUTH_API_KEY anywhere in the app.

2. CDK .env – For AWS CDK Deployment

The CDK .env file is used only once during the bootstrapping and deployment stages with AWS CDK. This is where you provide configurations required for setting up your cloud infrastructure.

Security Note

For sensitive variables like DATABASE_URL, it’s best to use a placeholder (e.g., postgresql://) during bootstrapping and deployment with AWS CDK. Don't worry, the real value will be securely passed through GitHub CI/CD to reduce security risks, as hardcoding the actual DATABASE_URL in the .env can still pose vulnerabilities if not handled cautiously.

3. CI/CD .env – For Secure Deployment

The CI/CD .env handles environment variables securely during automated deployments, typically configured in a file like main.yml for GitHub Actions. This file ensures secrets are not exposed in the deployment process.

How to Securely Pass New Environment Variables

  1. Add a new variable in cdk/.env with a value for both the production and staging sections. i.e. OAUTH_API_KEY=...

  2. Add sensitive data to GitHub Secrets. i.e. STAGING_OAUTH_API_KEY, PROD_OAUTH_API_KEY

  3. Configure .github/workflows/main.yml to read secrets during the deployment:

    - name: CDK Deployment
      env:
        ...
        OAUTH_API_KEY: ${{ secrets.STAGING_OAUTH_API_KEY }}

    Don't forget to add it for the Prod section as well

    - name: CDK Deployment
      env:
        ...
        OAUTH_API_KEY: ${{ secrets.PROD_OAUTH_API_KEY }}

    Also, add it under test section:

      test:
        name: Run Tests
        if: github.ref == 'refs/heads/staging'
        uses: ./.github/workflows/ssm-docker-test.yml
        secrets:
          ...
          OAUTH_API_KEY: ${{ secrets.STAGING_OAUTH_API_KEY }}
  4. Configure .github/workflows/ssm-docker-test.yml and add it under workflow_call:

    on:
      workflow_call:
        secrets:
          ...
          OAUTH_API_KEY:
            required: true

    Also, pass it here:

    - name: Build and test with Docker Compose (using host network)
      run: docker compose -f docker-compose.test.yml up --build --abort-on-container-exit
      env:
        ...
        OAUTH_API_KEY: ${{ secrets.OAUTH_API_KEY }}
  5. Pass it in cdk/bin/cdk.ts.

    new CdkStack(app, 'CdkStack', {
      oAuthApiKey: process.env.OAUTH_API_KEY,
    }
  6. Update interface in cdk/lib/cdk-stack.ts:

    export interface CdkStackProps extends StackProps {
      oAuthApiKey: string;
    }
  7. Provide the values to the ECS stack:

    new ECSStack(this, `ECS`, {
      oAuthApiKey: props.oAuthApiKey,
      ...
    }
  8. Configure in cdk/lib/nested/ecs-stack.ts:

    export interface ECSStackProps extends NestedStackProps {
      oAuthApiKey: string;
    }
    const {
          ...
          oAuthApiKey,
    } = props;
    const container = taskDefinition.addContainer(`AppContainer`, {
      environment: {
        ...
        OAUTH_API_KEY: oAuthApiKey,
      },
    });

⚠️ Adding environment variables to taskDefinition.addContainer is crucial, as it ensures they are passed to your backend.

  1. Update src/config/config.ts to validate variables for staging and production:

    .when(Joi.object({ NODE_ENV: Joi.string().valid('production', 'staging') }).unknown(), {
      then: Joi.object({
        OAUTH_API_KEY: Joi.string().required().description('OAUTH_API_KEY'),
      }),
    })
  2. Lastly, we need to pass this new env to run test. Go to docker-compose.test.yml and add your new variable.

      environment:
        ...
        OAUTH_API_KEY: ${OAUTH_API_KEY}

Summary

Managing environment variables effectively ensures a secure and maintainable development workflow. Here’s a quick recap:

  • Root .env: For local development, validated in src/config/config.ts.
  • CDK .env: Used for bootstrapping and deploying with AWS CDK. Avoid hardcoding sensitive data, as subsequent deployments with CI/CD can manage it more securely.
  • CI/CD env: Pass secrets securely during automated deployments through GitHub Actions. Also validated in src/config/config.ts.

By following these practices, you can handle environment variables across multiple stages while minimizing security risks and maintaining consistency.