Skip to content

Commit f05e587

Browse files
committed
Update server github actions.
1 parent c14225f commit f05e587

4 files changed

Lines changed: 125 additions & 201 deletions

File tree

.github/workflows/README.md

Lines changed: 25 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,56 @@
11
# ServiceStack mix GitHub Actions
2-
`release.yml` generated from `x mix release-ecr-aws`, this template in designed to help with automating CI deployments to AWS ECS and dedicated AWS ECS cluster.
3-
This is a cheap way to start without an AWS Application Load Balancer (ALB) and also be in a situation that will easier to add one once the web service needs additional scale or high availability.
2+
`release.yml` generated from `x mix release-ghr-vanilla`, this template in designed to help with CI deployment to a dedicated server with SSH access.
43

54
## Overview
6-
`release.yml` is designed to work with a ServiceStack app templates deploying directly to a single server in a dedicated ECS cluster via templated GitHub Actions.
5+
`release.yml` is designed to work with a ServiceStack app deploying directly to a single server via SSH. A docker image is built and stored on GitHub's `ghcr.io` docker registry when a GitHub Release is created.
76

8-
## Setup
9-
### Create unique ECS cluster
10-
For this setup, it is best to create a separate cluster as cluster will only have the single instance in it running.
11-
This pattern is to start from a good base with AWS ECS and automated CI deployment while avoiding the higher costs of needing to run an application load balancer (ALB).
7+
GitHub Actions specified in `release.yml` then copy files remotely via scp and use `docker-compose` to run the app remotely via SSH.
128

13-
If/when you can justify the cost of an ALB for easier scaling and zero downtime deployment, the GitHub Action `release.yml` can be slightly modified to be used with a re-created or different ECS Service that is configured to be used with an Application Load Balancer and Target Group.
9+
## Deployment server setup
10+
To get this working, a server needs to be setup with the following:
1411

15-
### Elastic IP (optional)
16-
The reason you might want to register this first is because we are only running one EC2 instance and hosting our own `nginx-proxy` on the same instance as the applications.
17-
Since an `A` record will be pointing there, one advantage of not using an auto-assigned IP is that we can reassign the elastic IP if for what ever reason the instance goes down or is lost.
12+
- SSH access
13+
- docker
14+
- docker-compose
15+
- ports 443 and 80 for web access of your hosted application
1816

19-
## Launch to EC2 Instance
20-
When launching the EC2 instance, you'll need to select an 'ECS optimized' AMI as the image used for your instance.
21-
### Choose AMI
22-
The easiest way to find the latest Amazon Linux 2 image for this is to go to the [AWS documentation for ECS-optimized AMIs and look up your region here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux).
17+
This can be your own server or any cloud hosted server like Digital Ocean, AWS, Azure etc.
2318

24-
Using the AMI ID (starts with `ami-`) at the bottom, search in the 'Community AMIs' tab on the first step of the `Launch EC2 Instance` wizard.
19+
When setting up your server, you'll want to use a dedicated SSH key for access to be used by GitHub Actions. GitHub Actions will need the *private* SSH key within a GitHub Secret to authenticate. This can be done via ssh-keygen and copying the public key to the authorized clients on the server.
2520

26-
### Choose Instance Type
27-
A t2.micro or larger will work fine, this pattern can be used to host multiple applications on the 1 server so if the number of applications gets larger, you might need a larger instance type.
28-
> Note this pattern is suitable for testing prototypes or low traffic applications as it is cost effective and makes it easy to bundle multiple apps onto 1 EC2 instance.
29-
30-
### Configure Instance
31-
Under `IAM role`, use the `ecsInstanceRole`, if this is not available, see [AWS documentation for the process of checking if it exists and creating it if needed](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html).
32-
33-
If you are *not* using your own generated Elastic IP, select `Enabled` for `Auto-assign Public IP`.
34-
35-
You will also want to add the following Userdata script (in the `Configure` step of the launch wizard) with your own `ECS_CLUSTER` value. This tells the ecs-agent running on the instance which ECS cluster the instance should join.
36-
37-
```bash
38-
#!/bin/bash
39-
cat <<EOS >/etc/ecs/ecs.config
40-
ECS_CLUSTER=my-cluster
41-
ECS_AVAILABLE_LOGGING_DRIVERS=["awslogs", "syslog"]
42-
ECS_ENABLE_CONTAINER_METADATA=true
43-
EOS
44-
```
45-
46-
Note down your cluster name as it will need to be used to create the cluster in ECS before it is visible.
47-
See [`ECS Container Agent Configuration`](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html) for more information.
48-
49-
### Add Storage
50-
The default of 30gb is fine but take into account how large/how many applications you'll have running.
51-
52-
### Configure Security Groups
53-
You'll want to expose at least ports 80 and 443.
54-
55-
### Setup Docker-compose and nginx-proxy
5621
To let your server handle multiple ServiceStack applications and automate the generation and management of TLS certificates, an additional docker-compose file is provided via the `x mix` template, `nginx-proxy-compose.yml`. This docker-compose file is ready to run and can be copied to the deployment server.
57-
> This is done via docker-compose rather than via ECS itself for simplicity.
58-
59-
First you'll need to install `docker-compose`.
60-
61-
```bash
62-
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
63-
sudo chmod +x /usr/local/bin/docker-compose
64-
```
65-
Run `docker-compose --version` to confirm.
66-
67-
To copy you can use scp or just creating a new file via server text editor to copy the short YML file over. For this example, we are going to copy it straight to the ~/ (home) directory.
68-
69-
```bash
70-
scp -i <path to private ssh key> ./nginx-proxy-compose.yml ec2-user@<server_floating_ip>:~/nginx-proxy.compose.yml
71-
```
7222

7323
For example, once copied to remote `~/nginx-proxy-compose.yml`, the following command can be run on the remote server.
7424

7525
```
7626
docker-compose -f ~/nginx-proxy-compose.yml up -d
7727
```
7828

79-
This will run an nginx reverse proxy along with a companion container that will watch for additional containers in the same docker network and attempt to initialize them with valid TLS certificates. This includes containers created and managed by the ECS agent.
80-
> If the container doesn't have the environment variable `VIRTUAL_HOST` set, it will be ignored.
29+
This will run an nginx reverse proxy along with a companion container that will watch for additional containers in the same docker network and attempt to initialize them with valid TLS certificates.
8130

8231
## GitHub Repository setup
8332
The `release.yml` assumes 6 secrets have been setup.
8433

85-
- AWS_ACCESS_KEY_ID - AWS access key for programmatic access to AWS APIs.
86-
- AWS_SECRET_ACCESS_KEY - AWS access secrets for programmatic access to AWS APIs.
87-
- AWS_REGION - default region for AWS API calls.
88-
- AWS_ECS_CLUSTER - Cluster name in ECS, this should match the value in your Userdata.
89-
- HOST_DOMAIN - Domain/sub-domain of your application, eg `vitepress-docs.example.com` .
34+
- CR_PAT - GitHub Personal Token with read/write access to packages.
35+
- DEPLOY_HOST - hostname used to SSH to, this can either be an IP address or subdomain with A record pointing to the server.
36+
- DEPLOY_PORT - SSH port, usually `22`.
37+
- DEPLOY_USERNAME - the username being logged into via SSH. Eg, `ubuntu`, `ec2-user`, `root` etc.
38+
- DEPLOY_KEY - SSH private key used to remotely access deploy server/app host.
9039
- LETSENCRYPT_EMAIL - Email address, required for Let's Encrypt automated TLS certificates.
9140

9241
These secrets can use the [GitHub CLI](https://cli.github.com/manual/gh_secret_set) for ease of creation. Eg, using the GitHub CLI the following can be set.
9342

9443
```bash
95-
gh secret set AWS_ACCESS_KEY_ID -b"<AWS_ACCESS_KEY_ID>"
96-
gh secret set AWS_SECRET_ACCESS_KEY -b"<AWS_SECRET_ACCESS_KEY>"
97-
gh secret set AWS_REGION -b"<AWS_REGION, eg us-east-1>"
98-
gh secret set AWS_ECS_CLUSTER -b"<AWS_ECS_CLUSTER, eg vitepress-docss>"
99-
gh secret set HOST_DOMAIN -b"<HOST_DOMAIN, eg vitepress-docs.example.com>"
100-
gh secret set LETSENCRYPT_EMAIL -b"<LETSENCRYPT_EMAIL, eg me@example.com>"
44+
gh secret set CR_PAT -b"<CR_PAT, Container Registry Personal Access Token>"
45+
gh secret set DEPLOY_HOST -b"<DEPLOY_HOST, domain or subdomain for your application and server host.>"
46+
gh secret set DEPLOY_PORT -b"<DEPLOY_PORT, eg SSH port, usually 22>"
47+
gh secret set DEPLOY_USERNAME -b"<DEPLOY_USERNAME, the username being logged into via SSH. Eg, `ubuntu`, `ec2-user`, `root` etc.>"
48+
gh secret set DEPLOY_KEY -b"<DEPLOY_KEY, SSH private key used to remotely access deploy server/app host.>"
49+
gh secret set LETSENCRYPT_EMAIL -b"<LETSENCRYPT_EMAIL, Email address for your TLS certificate generation, eg me@example.com>"
10150
```
10251

10352
These secrets are used to populate variables within GitHub Actions and other configuration files.
10453

105-
For the AWS access, a separate user specifically for deploying via GitHub Actions should be used.
106-
107-
The policies required for the complete initial setup will be:
108-
- `AmazonEC2ContainerRegistryFullAccess`
109-
- `AmazonECS_FullAccess`
110-
111-
Once the application is successfully deployed the first time, reduced access for both ECR and ECS can be used instead. For application updates, the GitHub Action can use the following policy.
112-
113-
```json
114-
{
115-
"Version": "2012-10-17",
116-
"Statement": [
117-
{
118-
"Sid": "VisualEditor0",
119-
"Effect": "Allow",
120-
"Action": [
121-
"ecr:GetRegistryPolicy",
122-
"ecr:PutImageTagMutability",
123-
"ecr:GetDownloadUrlForLayer",
124-
"ecr:DescribeRegistry",
125-
"ecr:GetAuthorizationToken",
126-
"ecr:ListTagsForResource",
127-
"ecr:UploadLayerPart",
128-
"ecr:ListImages",
129-
"ecr:PutImage",
130-
"ecr:UntagResource",
131-
"ecr:BatchGetImage",
132-
"ecr:CompleteLayerUpload",
133-
"ecr:DescribeImages",
134-
"ecr:TagResource",
135-
"ecr:DescribeRepositories",
136-
"ecr:InitiateLayerUpload",
137-
"ecr:BatchCheckLayerAvailability",
138-
"ecr:ReplicateImage",
139-
"ecr:GetRepositoryPolicy",
140-
"ecs:SubmitTaskStateChange",
141-
"ecs:UpdateContainerInstancesState",
142-
"ecs:RegisterContainerInstance",
143-
"ecs:DescribeTaskDefinition",
144-
"ecs:DescribeClusters",
145-
"ecs:ListServices",
146-
"ecs:UpdateService",
147-
"ecs:ListTasks",
148-
"ecs:ListTaskDefinitionFamilies",
149-
"ecs:RegisterTaskDefinition",
150-
"ecs:SubmitContainerStateChange",
151-
"ecs:StopTask",
152-
"ecs:DescribeServices",
153-
"ecs:ListContainerInstances",
154-
"ecs:DescribeContainerInstances",
155-
"ecs:DeregisterContainerInstance",
156-
"ecs:TagResource",
157-
"ecs:DescribeTasks",
158-
"ecs:UntagResource",
159-
"ecs:ListTaskDefinitions",
160-
"ecs:ListClusters"
161-
],
162-
"Resource": "*"
163-
}
164-
]
165-
}
166-
```
167-
> Further permission reduction can be done by reducing what resources can be accessed.
168-
> Application permissions can be controlled via `taskRoleArn`, see [AWS docs for details](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html).
169-
170-
## What's the process of the `release.yml`?
54+
## What's the process of `release.yml`?
17155

172-
![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/mix/release-ecr-aws-diagram.png)
56+
![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/mix/release-ghr-vanilla-diagram.png)

.github/workflows/release.yml

Lines changed: 48 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,82 @@
1-
name: Release Server
1+
name: Release
22
on:
33
release:
44
types: [published]
55
jobs:
6-
push_to_ecr:
6+
push_to_registry:
77
runs-on: ubuntu-20.04
88
steps:
9-
- name: Checkout
9+
- name: checkout
1010
uses: actions/checkout@v2
11-
11+
1212
- name: repository name fix
1313
run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
14-
15-
- name: Configure AWS credentials
16-
uses: aws-actions/configure-aws-credentials@v1
14+
15+
- name: Login to GitHub Container Registry
16+
uses: docker/login-action@v1
1717
with:
18-
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
19-
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
20-
aws-region: ${{ secrets.AWS_REGION }}
21-
22-
- name: Login to Amazon ECR
23-
id: login_ecr
24-
uses: aws-actions/amazon-ecr-login@v1
25-
26-
- name: Create ECR repo if not exists.
27-
env:
28-
ECR_REPOSITORY: ${{ env.image_repository_name }}
29-
run: aws ecr describe-repositories --repository-names ${ECR_REPOSITORY} || aws ecr create-repository --repository-name ${ECR_REPOSITORY}
30-
31-
- name: Build and push to ECR
32-
id: push_image_to_ecr
18+
registry: ghcr.io
19+
username: ${{ github.repository_owner }}
20+
password: ${{ secrets.CR_PAT }}
21+
22+
- name: Build and push Docker images
3323
uses: docker/build-push-action@v2.2.2
3424
with:
3525
file: Dockerfile
3626
context: .
3727
push: true
38-
tags: ${{ steps.login_ecr.outputs.registry }}/${{ env.image_repository_name }}:${{ github.event.release.tag_name }}
39-
40-
deploy_ecs:
41-
needs: push_to_ecr
28+
tags: ghcr.io/${{ env.image_repository_name }}:${{ github.event.release.tag_name }}
29+
30+
deploy_via_ssh:
31+
needs: push_to_registry
4232
runs-on: ubuntu-20.04
4333
steps:
4434
- name: checkout
4535
uses: actions/checkout@v2
4636

47-
- name: Configure AWS credentials
48-
uses: aws-actions/configure-aws-credentials@v1
49-
with:
50-
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
51-
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
52-
aws-region: ${{ secrets.AWS_REGION }}
53-
54-
- name: Login to Amazon ECR
55-
id: login_ecr
56-
uses: aws-actions/amazon-ecr-login@v1
57-
58-
- name: Repository name fix and env values setup
37+
- name: repository name fix and env
5938
run: |
6039
echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
61-
echo "domain=${{ secrets.HOST_DOMAIN }}" >> $GITHUB_ENV
40+
echo "domain=${{ secrets.DEPLOY_HOST }}" >> $GITHUB_ENV
6241
echo "letsencrypt_email=${{ secrets.LETSENCRYPT_EMAIL }}" >> $GITHUB_ENV
63-
echo "app_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]' | tr -d . | cut -d'/' -f2)" >> $GITHUB_ENV
64-
echo "cluster_name=${{ secrets.AWS_ECS_CLUSTER }}" >> $GITHUB_ENV
65-
echo "image_url=${{ steps.login_ecr.outputs.registry }}/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):${{ github.event.release.tag_name }}" >> $GITHUB_ENV
66-
echo "aws_region=${{ secrets.AWS_REGION }}" >> $GITHUB_ENV
6742
68-
- name: Populate task definition template
43+
- name: docker-compose file prep
6944
uses: danielr1996/envsubst-action@1.0.0
7045
env:
7146
RELEASE_VERSION: ${{ github.event.release.tag_name }}
72-
APP_NAME: ${{ env.app_name }}
73-
IMAGE_URL: ${{ env.image_url }}
47+
IMAGE_REPO: ${{ env.image_repository_name }}
7448
HOST_DOMAIN: ${{ env.domain }}
7549
LETSENCRYPT_EMAIL: ${{ env.letsencrypt_email }}
76-
AWS_REGION: ${{ env.aws_region }}
77-
CLUSTER_NAME: ${{ env.cluster_name }}
7850
with:
79-
input: deploy/task-definition-template.json
80-
output: deploy/task-definition.json
51+
input: deploy/docker-compose-template.yml
52+
output: deploy/NextJsTemplateApi-docker-compose.yml
8153

82-
- name: Create task definition if doesn't exist
83-
run: aws ecs describe-task-definition --task-definition ${{ env.app_name }} || aws ecs register-task-definition --cli-input-json file://deploy/task-definition.json
54+
- name: copy compose file via scp
55+
uses: appleboy/scp-action@v0.1.1
56+
with:
57+
host: ${{ secrets.DEPLOY_HOST }}
58+
username: ${{ secrets.DEPLOY_USERNAME }}
59+
port: ${{ secrets.DEPLOY_PORT }}
60+
key: ${{ secrets.DEPLOY_KEY }}
61+
source: "deploy/NextJsTemplateApi-docker-compose.yml"
62+
target: "~/"
8463

85-
- name: Create ECS Service if not exists.
86-
run: aws ecs describe-services --cluster ${{ env.cluster_name }} --services ${{ env.app_name }} | jq '.services[0]' -e || aws ecs create-service --cluster ${{ env.cluster_name }} --service-name ${{ env.app_name }} --task-definition ${{ env.app_name }} --desired-count 1
64+
- name: Set the value
65+
run: |
66+
echo "GH_TOKEN=${{ secrets.CR_PAT }}" >> $GITHUB_ENV
67+
echo "USERNAME=${{ secrets.DEPLOY_USERNAME }}" >> $GITHUB_ENV
8768
88-
- name: Deploy new revision of the task definition
89-
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
69+
- name: remote docker-compose up via ssh
70+
uses: appleboy/ssh-action@v0.1.4
71+
env:
72+
APPTOKEN: ${{ env.GH_TOKEN }}
73+
USERNAME: ${{ env.USERNAME }}
9074
with:
91-
task-definition: deploy/task-definition.json
92-
service: ${{ env.app_name }}
93-
cluster: ${{ env.cluster_name }}
94-
force-new-deployment: true
75+
host: ${{ secrets.DEPLOY_HOST }}
76+
username: ${{ secrets.DEPLOY_USERNAME }}
77+
key: ${{ secrets.DEPLOY_KEY }}
78+
port: ${{ secrets.DEPLOY_PORT }}
79+
envs: APPTOKEN,USERNAME
80+
script: |
81+
echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin
82+
docker-compose -f ~/deploy/NextJsTemplateApi-docker-compose.yml up -d

deploy/docker-compose-template.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: "3.9"
2+
services:
3+
NextJsTemplateApi:
4+
image: ghcr.io/${IMAGE_REPO}:${RELEASE_VERSION}
5+
restart: always
6+
network_mode: bridge
7+
ports:
8+
- "80"
9+
environment:
10+
VIRTUAL_HOST: ${HOST_DOMAIN}
11+
LETSENCRYPT_HOST: ${HOST_DOMAIN}
12+
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}

deploy/nginx-proxy-compose.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
version: '2'
2+
3+
services:
4+
nginx-proxy:
5+
image: jwilder/nginx-proxy
6+
container_name: nginx-proxy
7+
restart: always
8+
ports:
9+
- "80:80"
10+
- "443:443"
11+
volumes:
12+
- conf:/etc/nginx/conf.d
13+
- vhost:/etc/nginx/vhost.d
14+
- html:/usr/share/nginx/html
15+
- dhparam:/etc/nginx/dhparam
16+
- certs:/etc/nginx/certs:ro
17+
- /var/run/docker.sock:/tmp/docker.sock:ro
18+
network_mode: bridge
19+
20+
letsencrypt:
21+
image: jrcs/letsencrypt-nginx-proxy-companion:2.0
22+
container_name: nginx-proxy-le
23+
restart: always
24+
environment:
25+
- DEFAULT_EMAIL=you@example.com
26+
volumes_from:
27+
- nginx-proxy
28+
volumes:
29+
- certs:/etc/nginx/certs:rw
30+
- acme:/etc/acme.sh
31+
- /var/run/docker.sock:/var/run/docker.sock:ro
32+
network_mode: bridge
33+
34+
volumes:
35+
conf:
36+
vhost:
37+
html:
38+
dhparam:
39+
certs:
40+
acme:

0 commit comments

Comments
 (0)