Skip to content

Commit 3a6227f

Browse files
committed
docs: deployment posts improve
1 parent f0c87e5 commit 3a6227f

File tree

5 files changed

+941
-155
lines changed

5 files changed

+941
-155
lines changed

adminforth/documentation/blog/2024-08-05-chatgpt/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Here is how it looks in action:
1515

1616
<!-- truncate -->
1717

18-
## Simple controls
18+
# Simple controls
1919

2020
To control plugin we use our open-source [vue-suggestion-input](https://github.com/devforth/vue-suggestion-input).
2121
It allows to:

adminforth/documentation/blog/2024-11-14-compose-ec2-deployment-ci/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ data "aws_vpc" "default" {
125125
default = true
126126
}
127127
128-
129128
resource "aws_eip" "eip" {
130129
domain = "vpc"
131130
}
131+
132132
resource "aws_eip_association" "eip_assoc" {
133133
instance_id = aws_instance.app_instance.id
134134
allocation_id = aws_eip.eip.id

adminforth/documentation/blog/2025-02-19-compose-ec2-deployment-ci-registry/index.md

Lines changed: 23 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
slug: compose-ec2-deployment-github-actions-registry
3-
title: "IaaC Simplified: Automating EC2 Deployments with GitHub Actions, Terraform, Docker & Distribution Registry"
3+
title: "Amazon EC2 Deployments with GitHub Actions, Terraform, Docker & Amazon ECR"
44
authors: ivanb
55
tags: [aws, terraform, github-actions]
66
description: "The ultimate step-by-step guide to cost-effective, build-time-efficient, and easy managable EC2 deployments using GitHub Actions, Terraform, Docker, and a self-hosted registry."
@@ -11,90 +11,42 @@ image: "/ogs/ga-tf-aws.jpg"
1111
![alt text](ga-tf-aws.jpg)
1212

1313

14-
This guide shows how to deploy own Docker apps (with AdminForth as example) to Amazon EC2 instance with Docker and Terraform involving Docker self-hosted registry.
15-
16-
Needed resources:
17-
- GitHub actions Free plan which includes 2000 minutes per month (1000 of 2-minute builds per month - more then enough for many projects, if you are not running tests). Extra builds would cost `0.008$` per minute.
18-
- AWS account where we will auto-spawn EC2 instance. We will use `t3a.small` instance (2 vCPUs, 2GB RAM) which costs `~14$` per month in `us-east-1` region (cheapest region). Also it will take `$2` per month for EBS gp2 storage (20GB) for EC2 instance
19-
20-
Registry will be auto-spawned on EC2 instance, so no extra costs for it. GitHub storage is not used as well, so no costs for it as well.
21-
22-
The setup shape:
23-
- Build is done using IaaC approach with HashiCorp Terraform, so almoast no manual actions are needed from you. Every resource including EC2 server instance is described in code which is commited to repo.
24-
- Docker build process is done on GitHub actions server, so EC2 server is not overloaded with builds
25-
- Changes in infrastructure including changing server type, adding S3 Bucket, changing size of sever disk is also can be done by commiting code to repo.
26-
- Docker images and cache are stored on EC2 server, so no extra costs for Docker registry are needed.
27-
- Total build time for average commit to AdminForth app (with Vite rebuilds) is around 2 minutes.
14+
This guide is a hackers extended addition of [Deploying AdminForth to EC2 with Amazon ECR](/blog/compose-aws-ec2-ecr-terraform-github-actions/). The key difference in this post that we will not use Amazon ECR but self-host registry on EC2 itself. Automatically from terraform. And will see whether we will win something in terms of build time.
2815

2916
<!-- truncate -->
3017

3118

32-
# Building on CI versus building on EC2?
33-
34-
Previously we had a blog post about [deploying AdminForth to EC2 with Terraform without registry](/blog/compose-ec2-deployment-github-actions/). That method might work well but has a significant disadvantage - build process happens on EC2 itself and uses EC2 RAM and CPU. This can be a problem if your EC2 instance is well-loaded without extra free resources. Moreover, low-end EC2 instances have a small amount of RAM and CPU, so build process which involves vite/tsc/etc can be slow or even fail.
35-
36-
So obviously to solve this problem we need to move the build process to CI, however it introduces new chellenges and we will solve them in this post.
19+
## Costs for Amazon ECR vs consts for self-hosted registry on EC2
3720

38-
Quick difference between approaches from previous post and current post:
21+
Most of AWS services are formed from EC2 prices plus some extra overhead for own cost. In same way, Amazon ECR pricing is pretty same.
3922

40-
| Feature | Without Registry | With Registry |
23+
| Feature | Amazon ECR | Self-hosted registry on EC2 |
4124
| --- | --- | --- |
42-
| How and where docker build happens | Source code is rsync-ed from CI to EC2 and docker build is done there | Docker build is done on CI and docker image is pushed to registry (in this post we run registry automatically on EC2) |
43-
| How Docker build layers are cached | Cache is stored on EC2 | GitHub actions has no own Docker cache out of the box, so it should be stored in dedicated place (we use self-hosted registry on the EC2 as it is free) |
44-
| Advantages | Simpler setup with less code (we don't need code to run and secure registry, and don't need extra cache setup as is naturally persisted on EC2). | Build is done on CI, so EC2 server is not overloaded. For most cases CI builds are faster than on EC2. Plus time is saved because we don't need to rsync source code to EC2 |
45-
| Disadvantages | Build on EC2 requires additional server RAM / overloads CPU | More terraform code is needed. Registry cache might require small extra space on EC2. Complexities to make it run from both local machine and CI |
46-
| Initial build time\* | 2m 48.412s | 3m 13.541s |
47-
| Rebuild time (changed `index.ts`)\*| 0m 34.520s | 0m 51.653s |
48-
49-
<sub>\* All tests done from local machine (Intel(R) Core(TM) Ultra 9 185H, Docker Desktop/WSL2 64 GB RAM, 300Mbps up/down) up to working state</sub>
50-
51-
52-
## Chellenges when you build on CI
25+
| Storage | $0.10 per GB/month | $0.10 per GB/month for gp2 EBS volume |
26+
| Data transfer for egress | $0.09 per GB | $0.09 per GB |
5327

54-
A little bit of theory.
28+
So as you can see there is still no difference in terms of cost. However the approach in this system allows to replace Amazon EC2 with any other cloud provider which does not charge for egress traffic.
5529

56-
When you move build process to CI you have to solve next chellenges:
57-
1) We need to deliver built docker images to EC2 somehow (and only we)
58-
2) We need to persist cache between builds
5930

60-
### Delivering images
31+
# Bechnmarking build time
6132

62-
#### Exporing images to tar files
33+
When I implmented this solution, I was curious whether it will be faster to build images on EC2 or on CI. So I did a little bit of testing.
34+
First I used results form [deploying AdminForth to EC2 with Terraform without registry](/blog/compose-ec2-deployment-github-actions/) where we built images on EC2. Then I did the same test but with self-hosted registry on EC2 and compared to [deploying AdminForth to EC2 with Amazon ECR](/blog/compose-aws-ec2-ecr-terraform-github-actions/) where we built images on CI and pushed to Amazon ECR.
6335

64-
Simplest option which you can find is save docker images to tar files and deliver them to EC2. We can easily do it in terraform (using `docker save -o ...` command on CI and `docker load ...` command on EC2). However this option has a significant disadvantage - it is slow. Docker images are big (always include all layers, without any options), so it takes infinity to do save/load and another infinity to transfer them to EC2 (via relatively slow rsync/SSH and relatively slow GitHub actions outbound connection).
65-
66-
#### Docker registry
67-
68-
Faster, right option which we will use here - involve Docker registry. Registry is a repository which stores docker images. It does it in a smart way - it saves each image as several layers, so if you will update last layer, then only last layer will be pushed to registry and then only last will be pulled to EC2.
69-
To give you row compare - whole-layers image might take `1GB`, but last layer created by `npm run build` command might take `50MB`. And most builds you will do only last layer changes, so it will be 20 times faster to push/pull last layer than whole image.
70-
And this is not all, registry uses TLS HTTP protocol so it is faster then SSH/rsync encrypted connection.
71-
72-
Of course you have to care about a way of registry authentication (so only you and your CI/EC2 can push/pull images).
73-
74-
What docker registry can you use? Pretty known options:
75-
1) Docker Hub - most famous. It is free for public images, so literally every opensource project uses it. However it is not free for private images, and you have to pay for it. In this post we are considering you might do development for commercial project with tight budget, so we will not use it.
76-
2) GHCR - Registry from GitHub. Has free plan but allows to store only 500MB and allows to transfer 1GB of traffic per month. Then you pay for every extra GB in storage (`$0.0008` per GB/day or `$0.24` per GB/month) and for every extra GB in traffic ($0.09 per GB). Probably small images will fit in free plan, but generally even alpine-based docker images are bigger than 500MB, so it is non-free option.
77-
3) Amazon ECR - Same as GHCR but from Amazon. Price is `$0.10` per GB of storage per month and `$0.09` per GB of data transfer. So it is cheaper than GHCR but still not free. But is good option.
78-
4) Self-hosted registry web system. In our software development company, we use Harbor. It is a powerful free open-source registry that can be installed to own server. It allows pushing and pulling without limit. Also, it has internal life-cycle rules that cleanup unnecessary images and layers. The main drawbacks of it are that it is not so fast to install and configure, plus you have to get a domain and another powerfull server to run it. So unless you are a software development company, it is not worth using it.
79-
5) Self-hosted minimal CNCF Distribution [registry](https://distribution.github.io/distribution/) on EC2 itself. So since we already have EC2, we can run registry on it directly. The `registry` container is pretty light-weight and easy to setup and it will not consume a lot of extra CPU/RAM on server. Plus images will be stored close to application so pull will be fast.
80-
81-
In the post we will use last (5th way). Our terraform will deploy registry automatically, so you don't have to do anything special.
82-
83-
### Persisting cache
36+
| Feature | Without Registry (build directly on EC2) | With self-hosted registry | With Amazon ECR |
37+
| --- | --- | --- |
38+
| Initial build time\* | 3m 13.541s | 2m 48.412s | 3m 54s |
39+
| Rebuild time (changed `index.ts`)\*| 0m 51.653s | 0m 54.120s |
8440

85-
Docker builds without layer cache persistence are possible but very slow. Most builds only change a couple of layers, and having no ability to cache them will cause the Docker builder to regenerate all layers from scratch. This can, for example, increase the Docker build time from a minute to ten minutes or even more.
41+
<sub>\* All tests done from local machine (Intel(R) Core(TM) Ultra 9 185H, Docker Desktop/WSL2 64 GB RAM, 300Mbps up/down) up to working state</sub>
8642

87-
Out of the box, GitHub Actions can't save Docker layers between builds, so you have to use external storage.
43+
So it indeed own self-hosted registry is faster then ECR and overall build time of pure AdminForth is faster then building on EC2. When ECR is slower then self-hosted registry, it is because of network speed.
8844

89-
> Though some CI systems can persist docker build cache, e.g. open-source self-hosted Woodpecker CI allows it out of the box. However GitHub actions which is pretty popular, reasonably can't allow such free storage to anyone
9045

91-
So when build-in Docker cache can't be used, there is one alternative - Docker BuildKit external cache.
92-
So BuildKit allows you to connect external storage. There are several options, but most sweet for us is using Docker registry as cache storage (not only as images storage to deliver them to application server).
46+
# Chellenges when you build on CI
9347

94-
> *BuildKit cache in Compose issue*
95-
> Previously we used docker compose to build & run our app, it can be used to both build, push and pull images, but has [issues with external cache connection](https://github.com/docker/compose/issues/11072#issuecomment-1848974315). While they are not solved we have to use `docker buildx bake` command to build images. It is not so bad, but is another point of configuration which we will cover in this post.
9648

97-
### Registry authorization and traffic encryption
49+
# Registry authorization and traffic encryption
9850

9951
Hosting custom CNCF registry, from other hand is a security responsibility.
10052

@@ -104,7 +56,7 @@ First of all we need to set some authorization to our registry so everyone who w
10456

10557
But this is not enough. Basic auth is not encrypted, so someone can perform MITM attack and get your credentials. So we need to encrypt traffic between CI and registry. We can do it by using TLS certificates. So we will generate self-signed TLS certificates, and attach them to our registry.
10658

107-
59+
Though the challenge is that we need to provide CA certificate to every daemon which will work with our registry. So we need to provide CA certificate to buildx daemon on CI, also if we want to do it from local machine, we need to provide CA certificate to local docker daemon.
10860

10961
# Practice - deploy setup
11062

@@ -540,7 +492,7 @@ resource "null_resource" "sync_files_and_run" {
540492
echo '{"auths":{"appserver.local:5000":{"auth":"'$(echo -n "ci-user:$(cat ./.keys/registry.pure)" | base64 -w 0)'"}}}' > ~/.docker/config.json
541493
542494
echo "Running build"
543-
docker buildx bake --progress=plain --push --allow=fs.read=.. --allow network.host
495+
docker buildx bake --progress=plain --push --allow=fs.read=..
544496
545497
# compose temporarily it is not working https://github.com/docker/compose/issues/11072#issuecomment-1848974315
546498
# docker compose --progress=plain -p app -f ./compose.yml build --push
@@ -560,7 +512,7 @@ resource "null_resource" "sync_files_and_run" {
560512
# Run docker compose after files have been copied
561513
provisioner "remote-exec" {
562514
inline = [<<-EOF
563-
# wait for docker to be installed and started
515+
# login to docker registry
564516
cat /home/ubuntu/registry-auth/registry.pure | docker login localhost:5000 -u ci-user --password-stdin
565517
566518
cd /home/ubuntu/app/deploy
@@ -855,88 +807,6 @@ Now open GitHub actions file and add it to the `env` section:
855807
In the same way you can add any other secrets to your GitHub actions.
856808

857809

858-
### Out of space on EC2 instance? Extend EBS volume
859-
860-
861-
To upgrade EBS volume size you have to do next steps:
862-
863-
In `main.tf` file:
864-
865-
```hcl title="main.tf"
866-
root_block_device {
867-
//diff-remove
868-
volume_size = 20 // Size in GB for root partition
869-
//diff-add
870-
volume_size = 40 // Size in GB for root partition
871-
volume_type = "gp2"
872-
}
873-
```
874-
875-
And run build.
876-
877-
This will increase physical size of EBS volume, but you have to increase filesystem size too.
878-
879-
Login to EC2 instance:
880-
881-
```bash
882-
ssh -i ./.keys/id_rsa ubuntu@<your_ec2_ip>
883-
```
884-
885-
> You can find your EC2 IP in AWS console by visiting EC2 -> Instances -> Your instance -> IPv4 Public IP
886-
887-
888-
Now run next commands:
889-
890-
```bash
891-
lsblk
892-
```
893-
894-
This would show something like this:
895-
896-
```bash
897-
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
898-
loop0 7:0 0 99.4M 1 loop /snap/core/10908
899-
nvme0n1 259:0 0 40G 0 disk
900-
└─nvme0n1p1 259:1 0 20G 0 part /
901-
```
902-
903-
Here we see that `nvme0n1` is our disk and `nvme0n1p1` is our partition.
904-
905-
Now to extend partition run:
906-
907-
```bash
908-
sudo growpart /dev/nvme0n1 1
909-
sudo resize2fs /dev/nvme0n1p1
910-
```
911-
912-
This will extend partition to the full disk size. No reboot is needed.
913-
914-
915-
### Want slack notifications about build?
916-
917-
Create Slack channel and add [Slack app](https://slack.com/apps/A0F7YS25R-incoming-webhooks) to it.
918-
919-
Then create webhook URL and add it to GitHub secrets as `SLACK_WEBHOOK_URL`.
920-
921-
Add this steps to the end of your GitHub actions file:
922-
923-
```yml title=".github/workflows/deploy.yml"
924-
- name: Notify Slack on success
925-
if: success()
926-
run: |
927-
curl -X POST -H 'Content-type: application/json' --data \
928-
"{\"text\": \"✅ *${{ github.actor }}* successfully built *${{ github.ref_name }}* with commit \\\"${{ github.event.head_commit.message }}\\\".\n:link: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Build> | :link: <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|View Commit>\"}" \
929-
${{ secrets.SLACK_WEBHOOK_URL }}
930-
931-
- name: Notify Slack on failure
932-
if: failure()
933-
run: |
934-
curl -X POST -H 'Content-type: application/json' --data \
935-
"{\"text\": \"❌ *${{ github.actor }}* failed to build *${{ github.ref_name }}* with commit \\\"${{ github.event.head_commit.message }}\\\".\n:link: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Build> | :link: <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|View Commit>\"}" \
936-
${{ secrets.SLACK_WEBHOOK_URL }}
937-
938-
```
939-
940810

941811
### Want to run builds from your local machine?
942812

@@ -997,7 +867,7 @@ Check your public IP in Terraform output and add
997867
> Bad news is that instance public IP will be known only after first run, so some steps would fail because there will be no hosts mapping. However since EC2 provisioning takes some time it is even possible to copy IP from terminal and inser it to hosts file from first run 🤪
998868
999869
1000-
### 3. Using local build from multiple projects
870+
#### 3. Using local build from multiple projects
1001871
1002872
The easiest way would be probably to rename `appserver.local` to unique name for each project.
1003873
251 KB
Loading

0 commit comments

Comments
 (0)