AWS SAM in a Docker Dev Container
Tags: aws, serverless, lambda, docker
Published: 2020-03-25 17:36:19

When you look up AWS Lambda tutorials they usually walk you through using the web console to roll out a function-as-a-service. This is a nice parlor trick, but it doesn't help you do real work on Lambda because it's not reproducible. Until Github Actions can include “click 150 different things on console.aws.amazon.com”, reproducible builds means writing code on a developer machine, and making use of code-as-config and command-line tools to build and deploy.

So yesterday I took another crack at learning [AWS SAM](https://github.com/awslabs/serverless-application-model) (Serverless Application Model), which is a command-line-and-config-files abstraction layer on top of AWS [CloudFormation](https://aws.amazon.com/cloudformation/).

As is my habit these days, I put my experimental development environment inside a local docker container. That way the dev environment itself is reproducible, revision-controlled, and [available to others on Github](https://github.com/avdi/aws-sam).

The tricky bit here is that if you want to use SAM to test your Lambda functions locally, it needs to be able to start its own Docker containers. Can we start Docker containers *within* Docker containers? Yes! Not only that, it's possible to set up a Docker container to start up *sibling* containers instead of child containers, which avoids a lot of [Russian-doll](https://www.netflix.com/title/80211627) weirdness.

And happily, VS Code's [Remote-Containers](https://code.visualstudio.com/docs/remote/containers) project comes with a [Docker -in-Docker](https://github.com/Microsoft/vscode-dev-containers/tree/master/containers/docker-in-docker) config that's ready to use!

![img](https://i2.wp.com/avdi.codes/wp-content/uploads/2020/03/image.png?fit=770%2C250&ssl=1)

The secret sauce is this line in the “mounts” section of `.devcontainer/devcontainer.json`:

```
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
```

This binds the host Docker socket into the container docker socket, so that Docker commands are effectively processed by the host instead of inside the container.

I wanted a Python development environment inside the container, both because I wanted to work on a Python Lambda function and because I needed access to `pip` (see below). So I [exchanged](https://github.com/avdi/aws-sam/blob/bdcdde384bee7332c0bd3c94d8510605668143df/.devcontainer/Dockerfile#L8) the `debian` base image for `python:3.8-buster` in the [Dockerfile](https://github.com/avdi/aws-sam/blob/bdcdde384bee7332c0bd3c94d8510605668143df/.devcontainer/Dockerfile#L8).

The [AWS SAM Linux documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html) recommends installing the SAM CLI utility by first installing Homebrew. Which a) turned out to be a huge hassle; and b) I'm pretty sure was intended as a practical joke in the first place. (“Hey Steve, what do you want to bet I can get Linux dorks to install a whole second package management system just for our tools?”)

The *right* way to install SAM CLI (at least when you already have a Python base image) turns out to be via `pip`, an [easy one-liner in the Dockerfile](https://github.com/avdi/aws-sam/blob/bdcdde384bee7332c0bd3c94d8510605668143df/.devcontainer/Dockerfile#L58).

```
RUN pip install aws-sam-cli
```

I also wanted access to the base AWS CLI in my environment:

```
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
    && unzip awscliv2.zip \
    && ./aws/install
```

At this point I was nearly ready, but I needed to add my AWS credentials inside the container.

…or *did* I?

I had two objections to re-entering my AWS credentials inside a container:

1. I don't like scattering sensitive credentials around in many different places.
2. As much as possible I want my [revision-controlled development environment](https://github.com/avdi/aws-sam) to Just Work out of the box.

Here's the part I'm most proud of. Instead of entering my AWS creds inside the container, I mapped the credential file from my host machine into the container using another bind mount:

```
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.aws/credentials,target=/home/vscode/.aws/credentials,type=bind"
```

Note the use of `${localEnv:HOME}${localEnv:USERPROFILE}` to work on both Windows and UNIX-like Docker hosts.

At this point I was able to build and deploy a hello-world lambda function. I ran into some issues building and launching the function locally, but it appears to be a [known problem](https://github.com/aws/aws-toolkit-jetbrains/issues/483) so I'm hopeful that with some more reading and experimentation I'll be able to get over that hurdle soon.