This post by Farley Knight was really helpful in getting to know how to Dockerize and then deploy your rails app to AWS. Here are the settings that worked for me.
Dockerize and prepare your container
Dockerfile
FROM ruby:2.7.1-slim
# Update packages
RUN apt-get update -qq
# Install needed components
RUN apt-get install -y \
build-essential ca-certificates vim curl \
libpq-dev
# Get the latest node and yarn
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y nodejs yarn
# once working, consider cleaning up files
# && rm -rf /var/lib/apt/lists/*
ENV APP_HOME /app
ENV RAILS_SERVE_STATIC_FILES true
ENV RAILS_LOG_TO_STDOUT true
ADD . ${APP_HOME}
WORKDIR ${APP_HOME}
COPY Gemfile .
COPY Gemfile.lock .
RUN gem update bundler
# RUN bundle install --jobs 5 --without development test
RUN bundle install --jobs 5
# Copy my project over
COPY . ${APP_HOME}
# COPY package.json .
# COPY yarn.lock .
RUN yarn install --check-files
EXPOSE 8080
ENTRYPOINT ["sh", "./entrypoint.sh"]
```sh
#!/usr/bin/env bash
# Precompile assets
bundle exec rails assets:precompile
echo "Assets compiled."
# bundle exec yarn install --check-files
# If the database exists, migrate. Otherwise setup
bundle exec rails db:migrate 2>/dev/null | bundle exec rails db:create db:migrate
echo "Database migrated."
# Remove any old pre-existing pids for Rails
rm -f tmp/pids/server.pid
bundle exec rails server -b 0.0.0.0 -p 8080
version: '3.1'
services:
db:
image: postgres:12.4
restart: always
environment:
POSTGRES_PASSWORD: SOMETHING_LONG_AND_SECURE
web:
depends_on:
- db
build: .
ports:
- "8080:8080"
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: SOMETHING_LONG_AND_SECURE
RAILS_ENV: development
RAILS_MAX_THREADS: 5
volumes:
- ".:/app"
volumes:
db:
.dockerignore
.dockerignore
.git
log/
tmp/
Build and test locally
docker-compose build
docker-compose up
Setup AWS
Setup an elastic container registry.
Tag your web docker container.
Login to ecr.
Push your image.
Create your ECS cluster
Create your cluster.
Create a task. You define the container in here with the deployment environment config, so make sure you set your environment variables.
{
"ipcMode": null,
"executionRoleArn": null,
"containerDefinitions": [
{
"dnsSearchDomains": null,
"environmentFiles": null,
"logConfiguration": {
"logDriver": "awslogs",
"secretOptions": null,
"options": {
"awslogs-group": "/ecs/$TASKNAME",
"awslogs-region": "us-east-2",
"awslogs-stream-prefix": "ecs"
}
},
"entryPoint": null,
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 8080
}
],
"command": [
"sh",
"entrypoint.sh"
],
"linuxParameters": null,
"cpu": 0,
"environment": [
{
"name": "DB_HOST",
"value": "$RDS_DB_ENTRYPOINT"
},
{
"name": "DB_PASSWORD",
"value": "$LONGPASSWORD"
},
{
"name": "DB_PORT",
"value": "5432"
},
{
"name": "DB_USER",
"value": "postgres"
},
{
"name": "RAILS_ENV",
"value": "production"
},
{
"name": "SECRET_KEY_BASE",
"value": "$USE_RAILS_SECRET_TO_GENERATE_A_NEW_SECRET"
}
],
"resourceRequirements": null,
"ulimits": null,
"dnsServers": null,
"mountPoints": [],
"workingDirectory": null,
"secrets": null,
"dockerSecurityOptions": null,
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"stopTimeout": null,
"image": "$COPY_ARN_FROM_YOUR_DOCKER_IMAGE_IN_ECR",
"startTimeout": null,
"firelensConfiguration": null,
"dependsOn": null,
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": null,
"hostname": null,
"extraHosts": null,
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": null,
"systemControls": null,
"privileged": null,
"name": "web5"
}
],
"placementConstraints": [],
"memory": "512",
"taskRoleArn": null,
"compatibilities": [
"EC2"
],
"taskDefinitionArn": "arn:aws:ecs:us-east-2:569876213943:task-definition/$TASKNAME:1",
"family": "$TASKNAME",
"requiresAttributes": [
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.ecr-auth"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
}
],
"pidMode": null,
"requiresCompatibilities": [
"EC2"
],
"networkMode": null,
"cpu": "1024",
"revision": 1,
"status": "ACTIVE",
"inferenceAccelerators": null,
"proxyConfiguration": null,
"volumes": []
}
Then create a service in the cluster.
If it works correctly, after creating the service, it will start and create an image.
To login to your server
Make sure your routes support SSH, and your machines are connected to your public subnet.
Make sure you got the .pem file (and make sure chmod 0400 the file otherwise it will not work for ssh).
Get the endpoint of your ECS instance
ssh -i downloaded.pem ec2-user@ecID.us-east-2.compute.amazonaws.com
You use the ec2-user to login to the docker host (Amazon Linux 2, remember to use yum for updates).
To access your docker file
docker ps
docker exec -it IMAGE_ID /bin/bash
How to push an update to your server
After updating and testing your code locally, you update the AWS server, but forcing a new deployment of the service.
Build your new docker image
docker-compose build
docker-compose up
Docker Compose tags it with latest, you need to tag your destination aws tag with latest as well
docker tag docker1_web:latest 569876213943.dkr.ecr.us-east-2.amazonaws.com/tutorial:latest
Push it to aws
docker push 569876213943.dkr.ecr.us-east-2.amazonaws.com/tutorial:latest
Then ask for an update service.
As noted in this stack overflow post this will only work if you change the service to allow the Minimum Healthy Percent to be 0, which will let it shut down your instance and deploy the next one (as noted: this can create some downtime). If you have a load balancer then you can let the number of tasks be greater than 1 which will allow it to swap as well.
aws ecs update-service –cluster
aws ecs update-service –cluster tutorial-cluster5 –service tutorial-docker-service5 –force-new-deployment