Skip to content

Commit f0c87e5

Browse files
committed
docs: improve deployment posts
1 parent 819c644 commit f0c87e5

File tree

2 files changed

+81
-36
lines changed
  • adminforth/documentation/blog
    • 2024-11-14-compose-ec2-deployment-ci
    • 2025-02-19-compose-ec2-deployment-ci-registry

2 files changed

+81
-36
lines changed

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

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,18 @@ Create file `Dockerfile` in `myadmin`:
2222

2323
```Dockerfile title="./myadmin/Dockerfile"
2424
# use the same node version which you used during dev
25-
FROM node:20-alpine
25+
FROM node:22-alpine
2626
WORKDIR /code/
2727
ADD package.json package-lock.json /code/
2828
RUN npm ci
2929
ADD . /code/
3030
RUN --mount=type=cache,target=/tmp npx tsx bundleNow.ts
31-
CMD ["npm", "run", "startLive"]
31+
CMD ["sh", "-c", "npm run migrate:prod && npm run prod"]
3232
```
3333

34-
3534
## Step 2 - compose.yml
3635

37-
create folder `deploy` and create file `compose.yml` inside:
36+
Create folder `deploy` and create file `compose.yml` inside:
3837

3938
```yml title="deploy/compose.yml"
4039

@@ -51,10 +50,10 @@ services:
5150
- "/var/run/docker.sock:/var/run/docker.sock:ro"
5251

5352
myadmin:
54-
build: ./myadmin
53+
build: ../myadmin
5554
restart: always
5655
env_file:
57-
- ./myadmin/.env
56+
- ./myadmin/.env.secrets.prod
5857
volumes:
5958
- myadmin-db:/code/db
6059
labels:
@@ -88,12 +87,12 @@ Create `deploy/.gitignore` file with next content:
8887
*.tfstate.*
8988
*.tfvars
9089
tfplan
90+
.env.secrets.prod
9191
```
9292

9393
## Step 5 - Main terraform file main.tf
9494

9595

96-
9796
First of all install Terraform as described here [terraform installation](https://developer.hashicorp.com/terraform/install#linux).
9897

9998

@@ -102,7 +101,7 @@ Create file `main.tf` in `deploy` folder:
102101
```hcl title="deploy/main.tf"
103102
104103
locals {
105-
app_name = "<your_app_name>"
104+
app_name = "<your_app_name>" # replace with your app name
106105
aws_region = "eu-central-1"
107106
}
108107
@@ -230,13 +229,34 @@ resource "aws_instance" "app_instance" {
230229
systemctl start docker
231230
systemctl enable docker
232231
usermod -a -G docker ubuntu
232+
233+
echo "done" > /home/ubuntu/user_data_done
233234
EOF
234235
235236
tags = {
236237
Name = "${local.app_name}-instance"
237238
}
238239
}
239240
241+
resource "null_resource" "wait_for_user_data" {
242+
provisioner "remote-exec" {
243+
inline = [
244+
"echo 'Waiting for EC2 software install to finish...'",
245+
"while [ ! -f /home/ubuntu/user_data_done ]; do echo '...'; sleep 2; done",
246+
"echo 'EC2 software install finished.'"
247+
]
248+
249+
connection {
250+
type = "ssh"
251+
user = "ubuntu"
252+
private_key = file("./.keys/id_rsa")
253+
host = aws_eip_association.eip_assoc.public_ip
254+
}
255+
}
256+
257+
depends_on = [aws_instance.app_instance]
258+
}
259+
240260
resource "null_resource" "sync_files_and_run" {
241261
# Use rsync to exclude node_modules, .git, db
242262
provisioner "local-exec" {
@@ -262,16 +282,12 @@ resource "null_resource" "sync_files_and_run" {
262282
# Run docker compose after files have been copied
263283
provisioner "remote-exec" {
264284
inline = [
265-
# fail bash specially and intentionally to stop the script on error
266-
"bash -c 'while ! command -v docker &> /dev/null; do echo \"Waiting for Docker to be installed...\"; sleep 1; done'",
267-
"bash -c 'while ! docker info &> /dev/null; do echo \"Waiting for Docker to start...\"; sleep 1; done'",
268-
269285
# please note that prune might destroy build cache and make build slower, however it releases disk space
270286
"docker system prune -f",
271287
# "docker buildx prune -f --filter 'type!=exec.cachemount'",
272288
"cd /home/ubuntu/app/deploy",
273289
# COMPOSE_FORCE_NO_TTY is needed to run docker compose in non-interactive mode and prevent stdout mess up
274-
"COMPOSE_FORCE_NO_TTY=1 docker compose -p app -f compose.yml up --build -d"
290+
"COMPOSE_BAKE=true docker compose --progress=plain -p app -f compose.yml up --build -d"
275291
]
276292
277293
connection {
@@ -287,7 +303,7 @@ resource "null_resource" "sync_files_and_run" {
287303
always_run = timestamp()
288304
}
289305
290-
depends_on = [aws_instance.app_instance, aws_eip_association.eip_assoc]
306+
depends_on = [null_resource.wait_for_user_data, aws_eip_association.eip_assoc]
291307
}
292308
293309
@@ -310,6 +326,10 @@ resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
310326
status = "Enabled"
311327
id = "Keep only the latest version of the state file"
312328
329+
filter {
330+
prefix = ""
331+
}
332+
313333
noncurrent_version_expiration {
314334
noncurrent_days = 30
315335
}
@@ -333,8 +353,6 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state"
333353
}
334354
}
335355
}
336-
337-
338356
```
339357

340358
> 👆 Replace `<your_app_name>` with your app name (no spaces, only underscores or letters)
@@ -424,6 +442,11 @@ jobs:
424442
with:
425443
terraform_version: 1.10.1
426444
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
445+
- name: Prepare env
446+
run: |
447+
echo "ADMINFORTH_SECRET=$VAULT_ADMINFORTH_SECRET" > deploy/.env.secrets.prod
448+
env:
449+
VAULT_ADMINFORTH_SECRET: ${{ secrets.VAULT_ADMINFORTH_SECRET }}
427450
- name: Start building
428451
env:
429452
VAULT_AWS_ACCESS_KEY_ID: ${{ secrets.VAULT_AWS_ACCESS_KEY_ID }}
@@ -477,6 +500,6 @@ Go to your GitHub repository, then `Settings` -> `Secrets` -> `New repository se
477500
- `VAULT_AWS_SECRET_ACCESS_KEY` - your AWS secret key
478501
- `VAULT_SSH_PRIVATE_KEY` - make `cat ~/.ssh/id_rsa` and paste to GitHub secrets
479502
- `VAULT_SSH_PUBLIC_KEY` - make `cat ~/.ssh/id_rsa.pub` and paste to GitHub secrets
480-
503+
- `VAULT_ADMINFORTH_SECRET` - your AdminForth secret - random string, for example `openssl rand -base64 32 | tr -d '\n'`
481504

482505
Now you can push your changes to GitHub and see how it will be deployed automatically.

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

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ Previously we had a blog post about [deploying AdminForth to EC2 with Terraform
3535

3636
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.
3737

38-
3938
Quick difference between approaches from previous post and current post:
4039

4140
| Feature | Without Registry | With Registry |
@@ -44,8 +43,11 @@ Quick difference between approaches from previous post and current post:
4443
| 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) |
4544
| 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 |
4645
| 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 |
47-
| Initial build time *from local machine up to working state | 2m 48.412s | |
48-
| Rebuild time *from local machine, no docker cache changed `index.ts`| 0m 34.520s | |
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+
4951

5052
## Chellenges when you build on CI
5153

@@ -117,13 +119,13 @@ Create file `Dockerfile` in `myadmin`:
117119

118120
```Dockerfile title="./myadmin/Dockerfile"
119121
# use the same node version which you used during dev
120-
FROM node:20-alpine
122+
FROM node:22-alpine
121123
WORKDIR /code/
122124
ADD package.json package-lock.json /code/
123125
RUN npm ci
124126
ADD . /code/
125127
RUN --mount=type=cache,target=/tmp npx tsx bundleNow.ts
126-
CMD ["npm", "run", "migrateLiveAndStart"]
128+
CMD ["sh", "-c", "npm run migrate:prod && npm run prod"]
127129
```
128130

129131
### Step 1.1 - Create bundleNow.ts
@@ -159,9 +161,18 @@ Make sure you are not calling bundleNow in `index.ts` file for non-development m
159161
```json title="./myadmin/package.json"
160162
...
161163
"scripts": {
162-
...
163-
"migrateLiveAndStart": "npx --yes prisma migrate deploy && tsx index.ts",
164-
...
164+
"scripts": {
165+
"dev": "npm run _env:dev -- tsx watch index.ts",
166+
"prod": "npm run _env:prod -- tsx index.ts",
167+
"start": "npm run dev",
168+
169+
"makemigration": "npm run _env:dev -- npx --yes prisma migrate dev --create-only",
170+
"migrate:local": "npm run _env:dev -- npx --yes prisma migrate deploy",
171+
"migrate:prod": "npm run _env:prod -- npx --yes prisma migrate deploy",
172+
173+
"_env:dev": "dotenvx run -f .env -f .env.local --",
174+
"_env:prod": "dotenvx run -f .env.prod --"
175+
},
165176
}
166177
...
167178
```
@@ -197,7 +208,7 @@ services:
197208
pull_policy: always
198209
restart: always
199210
env_file:
200-
- .env.secrets.live
211+
- .env.secrets.prod
201212
environment:
202213
- NODE_ENV=production
203214
- DATABASE_URL=sqlite://.db.sqlite
@@ -248,7 +259,7 @@ Create `deploy/.gitignore` file with next content:
248259
*.tfstate.*
249260
*.tfvars
250261
tfplan
251-
.env.secrets.live
262+
.env.secrets.prod
252263
```
253264

254265
## Step 6 - buildx bake file
@@ -288,7 +299,6 @@ locals {
288299
aws_region = "us-east-1"
289300
}
290301
291-
292302
provider "aws" {
293303
region = local.aws_region
294304
profile = "myaws"
@@ -308,7 +318,6 @@ data "aws_vpc" "default" {
308318
default = true
309319
}
310320
311-
312321
resource "aws_eip" "eip" {
313322
domain = "vpc"
314323
}
@@ -783,7 +792,7 @@ jobs:
783792
784793
- name: Prepare env
785794
run: |
786-
echo "ADMINFORTH_SECRET=$VAULT_ADMINFORTH_SECRET" > deploy/.env.secrets.live
795+
echo "ADMINFORTH_SECRET=$VAULT_ADMINFORTH_SECRET" > deploy/.env.secrets.prod
787796
env:
788797
VAULT_ADMINFORTH_SECRET: ${{ secrets.VAULT_ADMINFORTH_SECRET }}
789798
@@ -832,9 +841,9 @@ Now open GitHub actions file and add it to the `env` section:
832841
```yml title=".github/workflows/deploy.yml"
833842
- name: Prepare env
834843
run: |
835-
echo "ADMINFORTH_SECRET=$VAULT_ADMINFORTH_SECRET" > deploy/.env.secrets.live
844+
echo "ADMINFORTH_SECRET=$VAULT_ADMINFORTH_SECRET" > deploy/.env.secrets.prod
836845
//diff-add
837-
echo "OPENAI_API_KEY=$VAULT_OPENAI_API_KEY" >> deploy/.env.secrets.live
846+
echo "OPENAI_API_KEY=$VAULT_OPENAI_API_KEY" >> deploy/.env.secrets.prod
838847
//diff-add
839848
env:
840849
VAULT_ADMINFORTH_SECRET: ${{ secrets.VAULT_ADMINFORTH_SECRET }}
@@ -948,6 +957,7 @@ Create folder `deploy/.local` and create next files:
948957
```sh title=deploy/.local/create-builder.sh
949958
#!/bin/bash
950959
cd "$(dirname "$0")"
960+
docker buildx rm mybuilder || true
951961
docker buildx create --name mybuilder --driver docker-container --use --config ./buildkitd.toml
952962
```
953963

@@ -957,9 +967,9 @@ Now create builder:
957967
bash .local/create-builder.sh
958968
```
959969

960-
#### 2. You need to deliver envs locally
970+
#### 2. You need to deliver same secrets from local machine as from CI vault
961971

962-
Create file `deploy/.env.secrets.live` with next content:
972+
Create file `deploy/.env.secrets.prod` with next content:
963973

964974
```sh
965975
ADMINFORTH_SECRET=<your secret>
@@ -969,7 +979,8 @@ Please note that if you are running builds both from GA and local, the `ADMINFOR
969979

970980
#### 2. You need to add app.server.local to your hosts file (Windows/WSL only)
971981

972-
> This step is not needed on Linux / Mac because
982+
> This step is not needed on Linux / Mac because teraform provisioner will autiomatically add it to `/etc/hosts` file.
983+
> However in WSL we can't modify Windows native hosts file, so we need to do it manually.
973984

974985
In power shell run
975986

@@ -981,4 +992,15 @@ Check your public IP in Terraform output and add
981992
982993
```
983994
<your public ip> appserver.local
984-
```
995+
```
996+
997+
> 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 🤪
998+
999+
1000+
### 3. Using local build from multiple projects
1001+
1002+
The easiest way would be probably to rename `appserver.local` to unique name for each project.
1003+
1004+
Then you can put all certificate mappings to a `buildkitd.toml` and move it along with `create-builder.sh` script to a common folder, e.g. home
1005+
1006+

0 commit comments

Comments
 (0)