Mounting Configuration Files in Fargate

A lot of Docker images, like nginx, support configuration using files. The documentation recommends that you create the file locally and then mount it to your container with -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro. Other images, like grafana and redis, support similar configuration methods.

But this method doesn’t work on Fargate because the server running your containers doesn’t have access to your local files. So how can you mount configuration files into containers in Fargate?

One option is baking the configuration file into your image. The downside is that this requires building, storing, and maintaining your own image. It also makes changing your configuration much more difficult.

A simpler method is using a sidecar container that writes the configuration to a volume shared by both containers. The sidecar container uses images like bash or amazon/aws-cli. It can read the configuration from an environment variable, from SSM or even S3.

To add a sidecar container to your existing task definition:

  1. Define a transient volume. When doing this in Fargate Console select Bind Mount type.
  2. Add a new sidebar container definition to your task. Use bash or amazon/aws-cli as the image.
  3. Mount the new volume into your new sidecar container.
  4. Update the command of sidecar container to read the configuration and write it to the mounting point.
  5. Update your existing container definition to also mount the same volume to where the image is expecting the configuration file.
  6. Set your existing container to depend on the new sidecar container to avoid any race conditions.

For example, if we want to configure nginx container using the following configuration file, we can use bash to write it to /etc/nginx/nginx.conf. To avoid any issues with newlines, we will base64 encode the configuration file and put it in the environment of the sidecar container.

events {
  worker_connections  1024;
}

http {
  server {
    listen 80;
    location / {
      proxy_pass https://kichik.com;
    }
  }
}

All this takes just a few lines with CloudFormation but can be done using other APIs as well. As you can see, this template defines a task definition with two containers. One container is nginx itself, and the other is the sidecar container. Both of them mount the same volume. The main container depends on the sidecar container. The sidecar container takes the configuration from the environment, decodes it using base64 and writes it to /etc/nginx/nginx.conf. Since both containers use the same volume, the main container will see and use this configuration file.

Resources:
  FargateTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: 256
      Memory: 512
      Volumes:
        - Name: nginx-conf-vol
          Host: {}
      ContainerDefinitions:
        - Name: nginx
          Image: nginx
          Essential: true
          DependsOn:
          - Condition: COMPLETE
            ContainerName: nginx-config
          PortMappings:
            - ContainerPort: 80
          MountPoints:
            - ContainerPath: /etc/nginx
              SourceVolume: nginx-conf-vol
        - Name: nginx-config
          Image: bash
          Essential: false
          Command:
            - -c
            - echo $DATA | base64 -d - | tee /etc/nginx/nginx.conf
          Environment:
            - Name: DATA
              Value:
                Fn::Base64: |
                  events {
                    worker_connections  1024;
                  }
                  
                  http {
                    server {
                      listen 80;
                      location / {
                        proxy_pass https://kichik.com;
                      }
                    }
                  }
          MountPoints:
            - ContainerPath: /etc/nginx
              SourceVolume: nginx-conf-vol

After deploying this template, you can launch a Fargate task and the result will be a simple web server proxying all requests back to this blog.

This is a very raw example. You would usually want to enable logs, and get configuration from somewhere dynamic in production. But it shows the basics of this sidecar method and can be applied to any Docker image that requires mounting a configuration file.

3 thoughts on “Mounting Configuration Files in Fargate

  1. FYI (might be new features since you wrote the article)
    It is possible to embed the nginx configuration directly into the template via:

    Fn::Base64: |
    events {
    worker_connections 1024;
    }

    http {
    server {
    listen 80;
    location / {
    proxy_pass https://kichik.com;
    }
    }
    }

    if you need to substitute in parameters (or secrets!) you can do:
    Fn::Base64:
    Fn::Sub:
    – |
    events {
    worker_connections ${CONNECTIONS};
    }

    http {
    server {
    listen ${PORT};
    location / {
    proxy_pass https://${ENDPOINT}.com;
    }
    }
    }
    – ENDPOINT: “kichik.com”
    CONNECTIONS: “1024”
    PORT: “80”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.