Going offline with golang, serverless and dynamodb

3 min read

First of all, you do not need this! Really, Lambda (or other serverless platforms) is pretty cheap. Most of the time, development costs are covered with the free tier, and in the worst case, it will cost you less than one beer.

Anyway, there is a lot of noise and many tools related to offline development, so I was curious and tried to set up that stuff. The most common use case is an HTTP service backed by DynamoDB, so let's focus on that. Fortunately (or not?), there are tools that do all the hard work for you.

serverless-offline

serverless-offline is a serverless plugin which emulates AWS lambda and API gateway on your local machine.

Install the plugin:

$ npm install serverless-offline --save-dev

And the plugin to the plugins section in your serverless.yml:

plugins:
  - serverless-offline

That's it. After executing sls offline start command, the service is available on http://localhost:3000/dev.

Oh wait! Requests to one of the configured endpoints say my runtime is not supported.

offline: Failure: Unsupported runtime
Error: Unsupported runtime

Well, the Go runtime is supported only via the docker-runner, which is toggled with the --useDocker flag.

So sls offline start --useDocker spins the runtime in a Docker container, and I should be good now, shouldn't I? Let's move to the DynamoDB part.

note: at the time of writing I am using the plugin version v6.1.4

serverless-dynamodb-local

I am following the documentation and installing the plugin:

$ npm install --save [email protected]

Update the plugins section:

plugins:
  - serverless-offline
  - serverless-dynamodb-local

Install dynamodb local and start it:

$ sls plugin install -n serverless-dynamodb-local
$ sls dynamodb install
$ sls dynamodb start

Mocked DynamoDB will run on localhost and port 8000, so it's necessary to update the client configuration in the service code:

var db = dynamodb.New(session.New(&aws.Config{
	Region: aws.String("localhost"),
	Endpoint: aws.String("http://localhost:8000"),
}))

Note that in AWS, DynamoDB won't run on localhost; therefore, the DB must have different configuration in production. I use the IS_OFFLINE environment variable to distinguish between offline and production deployment.

provider:
  name: aws
  runtime: go1.x
  environment:
    IS_OFFLINE: ${env:IS_OFFLINE}
var db *dynamodb.DynamoDB

func init() {
	if os.Getenv("IS_OFFLINE") {
		db = ...
	} else {
		db = ...
	}
}

Start the application with sls offline start --useDocker, do some requests, and tadaaa ... the application is not working because it's not able to connect to localhost:8000, the port where DynamoDB is running.

$ netstat -ntlp | grep 8000
tcp6    0    0    :::8000    :::*    LISTEN    172892/java

The reason is that the runtime is on a different network since the default network mode for Docker is bridge mode. After inspecting the runtime container or digging in the plugin's codebase, I found a host mapping for a docker0 bridge gateway IP which is bound to the host.docker.internal hostname.

So let's fix the endpoint in the AWS config:

	Endpoint: aws.String("http://host.docker.internal:8000"),

Restart the service and voila ... API Gateway with DynamoDB emulator is working now, and the local development setup is finally ready. Enjoy!