TL;DR Ensure your AWS pipeline buildspec.yaml runs a hardhat compile to provide your runtime with the ABI artifacts.  

Running Web apps in Docker has become the generally accepted enterprise-grade solution. Through Docker, we exercise a great deal more control over the runtime environment, allowing maximum portability for our code. Ethereum web apps - aka Dapps - are no exception. Typically, we will create a build pipeline that responds to each code push, building a new Docker image from the new codebase.

JavaScript is the most common language used in Dapps, enabling developers to profit from frameworks like Hardhat to aid the development process. There are also mature, open-source libraries like Ethers.js for managing the interaction between JavaScript apps and the blockchain.

Local compilation

The Ethereum Virtual Machine (EVM) uses Bytecode, not a high-level programming language. As such, JavaScript needs an Application Binary Interface, or ABI, to know what methods a contract exposes to the outside world. ABIs and other files facilitating this translation from JS to Bytecode are commonly known as Artifacts.

Hardhat enables us to create Artifacts very simply. After having put your contracts in a folder called Contracts at the root of your repo, just run:

npx hardhat compile

This command will create a folder called artifacts at the root. This folder is essential for the execution of the App, so it is a critical thing to include when we’re building the Docker image. We need to have these files when we run our build pipeline.  

Dockerfile

We list the instructions to build and run our Docker image in our Dockerfile. The two dots mean ‘copy all files from root to the root inside the container’:

COPY . .

So, when we run the docker build command, we need the Artifacts to be at the root of wherever the Build pipeline is accessing our repo.  

gitignore

Like most frameworks, when you initialise your project Hardhat creates a .gitignore file for you. This .gitignore stops sensitive files from going into your repo, including your artifacts. Check the bottom of the standard Hardhat gitignore:

#Hardhat files
cache
artifacts

At your Build pipeline runtime, you won’t have these folders in your remote repo. The pipeline will need these as part of the build. It will fail when it doesn’t find them. We can either remove the reference to them in gitignore (not advised) or ensure our pipeline builds them as part of its execution.  

buildspec.yaml

The buildspec.yaml file tells your AWS build pipeline what steps to execute. Here, we need to instruct the build agent to run our hardhat compilation and create the artifacts folder.

In our pre_build phase, we must first ensure we have Hardhat in the runtime, which we do via npm install. We can execute our npx hardhat compile following this step, ensuring the contract ABIs are present.

Quick gotcha: the below buildspec.yaml won’t work unless you specify the AWS_ACCOUNT_ID as an environment variable. Not sure why this isn’t a default variable in CodeBuild/CodePipeline like AWS_DEFAULT_REGION is, but there you go.  

version: 0.2
phases:
install:
runtime-versions:
nodejs: 12
pre_build:
commands: - echo Running npm install... - npm install - npx hardhat compile - echo Logging into Amazon ECR... - aws --version - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.eu-west-2.amazonaws.com/<your-app-name>
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands: - echo Pushing the Docker images... - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG - echo Writing image definitions file... - printf '[{"name":"<your-app-name","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json