Skip to content

Commit d0bdcf0

Browse files
authored
Initial commit
0 parents  commit d0bdcf0

File tree

207 files changed

+60847
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

207 files changed

+60847
-0
lines changed

.deploy/docker-compose.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
version: "3.9"
2+
services:
3+
app:
4+
image: ghcr.io/${IMAGE_REPO}:${RELEASE_VERSION}
5+
restart: always
6+
ports:
7+
- "8080"
8+
container_name: ${APP_NAME}_app
9+
environment:
10+
VIRTUAL_HOST: ${HOST_DOMAIN}
11+
VIRTUAL_PORT: 8080 # New default ASP.NET port -> https://learn.microsoft.com/en-us/dotnet/core/compatibility/containers/8.0/aspnet-port
12+
LETSENCRYPT_HOST: ${HOST_DOMAIN}
13+
LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
14+
volumes:
15+
- app-mydb:/app/App_Data
16+
17+
app-migration:
18+
image: ghcr.io/${IMAGE_REPO}:${RELEASE_VERSION}
19+
restart: "no"
20+
container_name: ${APP_NAME}_app_migration
21+
profiles:
22+
- migration
23+
command: --AppTasks=migrate
24+
volumes:
25+
- app-mydb:/app/App_Data
26+
27+
networks:
28+
default:
29+
external: true
30+
name: nginx
31+
32+
volumes:
33+
app-mydb:

.deploy/nginx-proxy-compose.yml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
version: "3.9"
2+
3+
services:
4+
nginx-proxy:
5+
image: nginxproxy/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+
labels:
19+
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
20+
21+
letsencrypt:
22+
image: nginxproxy/acme-companion:2.2
23+
container_name: nginx-proxy-le
24+
restart: always
25+
depends_on:
26+
- "nginx-proxy"
27+
environment:
28+
29+
volumes:
30+
- certs:/etc/nginx/certs:rw
31+
- acme:/etc/acme.sh
32+
- vhost:/etc/nginx/vhost.d
33+
- html:/usr/share/nginx/html
34+
- /var/run/docker.sock:/var/run/docker.sock:ro
35+
36+
networks:
37+
default:
38+
name: nginx
39+
40+
volumes:
41+
conf:
42+
vhost:
43+
html:
44+
dhparam:
45+
certs:
46+
acme:

.github/workflows/README.md

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
## Overview
2+
3+
This template uses the deployment configurations for a ServiceStack .NET 8 application. The application is containerized using Docker and is set up to be automatically built and deployed via GitHub Actions. The recommended deployment target is a stand-alone Linux server running Ubuntu, with an NGINX reverse proxy also containerized using Docker, which a Docker Compose file is included in the template under the `.deploy` directory.
4+
5+
### Highlights
6+
- 🌐 **NGINX Reverse Proxy**: Utilizes an NGINX reverse proxy to handle web traffic and SSL termination.
7+
- 🚀 **GitHub Actions**: Leverages GitHub Actions for CI/CD, pushing Docker images to GitHub Container Registry and deploying them on a remote server.
8+
- 🐳 **Dockerized ServiceStack App**: The application is containerized, with the image built using `.NET 8`.
9+
- 🔄 **Automated Migrations**: Includes a separate service for running database migrations.
10+
11+
### Technology Stack
12+
- **Web Framework**: ServiceStack
13+
- **Language**: C# (.NET 8)
14+
- **Containerization**: Docker
15+
- **Reverse Proxy**: NGINX
16+
- **CI/CD**: GitHub Actions
17+
- **OS**: Ubuntu 22.04 (Deployment Server)
18+
19+
20+
21+
## Deployment Server Setup
22+
23+
To successfully host your ServiceStack applications, there are several components you need to set up on your deployment server. This guide assumes you're working on a standalone Linux server (Ubuntu is recommended) with SSH access enabled.
24+
25+
### Prerequisites
26+
27+
1. **SSH Access**: Required for GitHub Actions to communicate with your server.
28+
2. **Docker**: To containerize your application.
29+
3. **Docker-Compose**: For orchestrating multiple containers.
30+
4. **Ports**: 80 and 443 should be open for web access.
31+
5. **nginx-reverse-proxy**: For routing traffic to multiple ServiceStack applications and managing TLS certificates.
32+
33+
You can use any cloud-hosted or on-premises server like Digital Ocean, AWS, Azure, etc., for this setup.
34+
35+
### Step-by-Step Guide
36+
37+
#### 1. Install Docker and Docker-Compose
38+
39+
It is best to follow the [latest installation instructions on the Docker website](https://docs.docker.com/engine/install/ubuntu/) to ensure to have the correct setup with the latest patches.
40+
41+
#### 2. Configure SSH for GitHub Actions
42+
43+
Generate a dedicated SSH key pair to be used by GitHub Actions:
44+
45+
```bash
46+
ssh-keygen -t rsa -b 4096 -f ~/.ssh/github_actions
47+
```
48+
49+
Add the public key to the `authorized_keys` file on your server:
50+
51+
```bash
52+
cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys
53+
```
54+
55+
Then, add the *private* key to your GitHub Secrets as `DEPLOY_KEY` to enable GitHub Actions to SSH into the server securely.
56+
57+
#### 3. Set Up nginx-reverse-proxy
58+
59+
You should have a `docker-compose` file similar to the `nginx-proxy-compose.yml` in your repository. Upload this file to your server:
60+
61+
```bash
62+
scp nginx-proxy-compose.yml user@your_server:~/
63+
```
64+
65+
To bring up the nginx reverse proxy and its companion container for handling TLS certificates, run:
66+
67+
```bash
68+
docker compose -f ~/nginx-proxy-compose.yml up -d
69+
```
70+
71+
This will start an nginx reverse proxy along with a companion container. They will automatically watch for additional Docker containers on the same network and initialize them with valid TLS certificates.
72+
73+
74+
75+
## GitHub Repository Setup
76+
77+
Configuring your GitHub repository is an essential step for automating deployments via GitHub Actions. This guide assumes you have a `release.yml` workflow file in your repository's `.github/workflows/` directory, and your deployment server has been set up according to the [Deployment Server Setup](#Deployment-Server-Setup) guidelines.
78+
79+
### Secrets Configuration
80+
81+
Your GitHub Actions workflow requires the following secrets to be set in your GitHub repository:
82+
83+
1. **`DEPLOY_HOST`**: The hostname for SSH access. This can be either an IP address or a domain with an A-record pointing to your server.
84+
2. **`DEPLOY_USERNAME`**: The username for SSH login. Common examples include `ubuntu`, `ec2-user`, or `root`.
85+
3. **`DEPLOY_KEY`**: The SSH private key to securely access the deployment server. This should be the same key you've set up on your server for GitHub Actions.
86+
4. **`LETSENCRYPT_EMAIL`**: Your email address, required for Let's Encrypt automated TLS certificates.
87+
88+
#### Using GitHub CLI for Secret Management
89+
90+
You can conveniently set these secrets using the [GitHub CLI](https://cli.github.com/manual/gh_secret_set) like this:
91+
92+
```bash
93+
gh secret set DEPLOY_HOST --body="your-host-or-ip"
94+
gh secret set DEPLOY_USERNAME --body="your-username"
95+
gh secret set DEPLOY_KEY --bodyFile="path/to/your/ssh-private-key"
96+
gh secret set LETSENCRYPT_EMAIL --body="[email protected]"
97+
```
98+
99+
These secrets will populate environment variables within your GitHub Actions workflow and other configuration files, enabling secure and automated deployment of your ServiceStack applications.

.github/workflows/build.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Build
2+
3+
on:
4+
pull_request: {}
5+
push:
6+
branches:
7+
- '**' # matches every branch
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-22.04
12+
steps:
13+
- name: checkout
14+
uses: actions/checkout@v3
15+
16+
- name: Setup dotnet
17+
uses: actions/setup-dotnet@v3
18+
with:
19+
dotnet-version: '8.0'
20+
21+
- name: build
22+
run: dotnet build
23+
working-directory: .
24+
25+
- name: test
26+
run: |
27+
dotnet test
28+
if [ $? -eq 0 ]; then
29+
echo TESTS PASSED
30+
else
31+
echo TESTS FAILED
32+
exit 1
33+
fi
34+
working-directory: ./MyApp.Tests
35+

.github/workflows/release.yml

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
name: Release
2+
permissions:
3+
packages: write
4+
contents: write
5+
on:
6+
# Triggered on new GitHub Release
7+
release:
8+
types: [published]
9+
# Triggered on every successful Build action
10+
workflow_run:
11+
workflows: ["Build"]
12+
branches: [main,master]
13+
types:
14+
- completed
15+
# Manual trigger for rollback to specific release or redeploy latest
16+
workflow_dispatch:
17+
inputs:
18+
version:
19+
default: latest
20+
description: Tag you want to release.
21+
required: true
22+
23+
jobs:
24+
push_to_registry:
25+
runs-on: ubuntu-22.04
26+
if: ${{ github.event.workflow_run.conclusion != 'failure' }}
27+
steps:
28+
# Checkout latest or specific tag
29+
- name: checkout
30+
if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }}
31+
uses: actions/checkout@v3
32+
- name: checkout tag
33+
if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }}
34+
uses: actions/checkout@v3
35+
with:
36+
ref: refs/tags/${{ github.event.inputs.version }}
37+
38+
# Assign environment variables used in subsequent steps
39+
- name: Env variable assignment
40+
run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
41+
# TAG_NAME defaults to 'latest' if not a release or manual deployment
42+
- name: Assign version
43+
run: |
44+
echo "TAG_NAME=latest" >> $GITHUB_ENV
45+
if [ "${{ github.event.release.tag_name }}" != "" ]; then
46+
echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
47+
fi;
48+
if [ "${{ github.event.inputs.version }}" != "" ]; then
49+
echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV
50+
fi;
51+
if [ ! -z "${{ secrets.APPSETTINGS_PATCH }}" ]; then
52+
echo "HAS_APPSETTINGS_PATCH=true" >> $GITHUB_ENV
53+
else
54+
echo "HAS_APPSETTINGS_PATCH=false" >> $GITHUB_ENV
55+
fi;
56+
57+
- name: Login to GitHub Container Registry
58+
uses: docker/login-action@v2
59+
with:
60+
registry: ghcr.io
61+
username: ${{ github.actor }}
62+
password: ${{ secrets.GITHUB_TOKEN }}
63+
64+
- name: setup .net core
65+
uses: actions/setup-dotnet@v3
66+
with:
67+
dotnet-version: '8.0'
68+
69+
- name: Install x tool
70+
if: env.HAS_APPSETTINGS_PATCH == 'true'
71+
run: dotnet tool install -g x
72+
73+
- name: Apply Production AppSettings
74+
if: env.HAS_APPSETTINGS_PATCH == 'true'
75+
working-directory: ./MyApp
76+
run: |
77+
cat <<EOF >> appsettings.json.patch
78+
${{ secrets.APPSETTINGS_PATCH }}
79+
EOF
80+
x patch appsettings.json.patch
81+
cat appsettings.json
82+
83+
# Build and push new docker image, skip for manual redeploy other than 'latest'
84+
- name: Build and push Docker image
85+
run: |
86+
dotnet publish --os linux --arch x64 -c Release -p:ContainerRepository=${{ env.image_repository_name }} -p:ContainerRegistry=ghcr.io -p:ContainerImageTags=${{ env.TAG_NAME }} -p:ContainerPort=80
87+
88+
deploy_via_ssh:
89+
needs: push_to_registry
90+
runs-on: ubuntu-22.04
91+
if: ${{ github.event.workflow_run.conclusion != 'failure' }}
92+
steps:
93+
# Checkout latest or specific tag
94+
- name: checkout
95+
if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }}
96+
uses: actions/checkout@v3
97+
- name: checkout tag
98+
if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }}
99+
uses: actions/checkout@v3
100+
with:
101+
ref: refs/tags/${{ github.event.inputs.version }}
102+
103+
- name: repository name fix and env
104+
run: |
105+
echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
106+
echo "TAG_NAME=latest" >> $GITHUB_ENV
107+
if [ "${{ github.event.release.tag_name }}" != "" ]; then
108+
echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
109+
fi;
110+
if [ "${{ github.event.inputs.version }}" != "" ]; then
111+
echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV
112+
fi;
113+
114+
- name: Create .env file
115+
run: |
116+
echo "Generating .env file"
117+
118+
echo "# Autogenerated .env file" > .deploy/.env
119+
echo "HOST_DOMAIN=${{ secrets.DEPLOY_HOST }}" >> .deploy/.env
120+
echo "LETSENCRYPT_EMAIL=${{ secrets.LETSENCRYPT_EMAIL }}" >> .deploy/.env
121+
echo "APP_NAME=${{ github.event.repository.name }}" >> .deploy/.env
122+
echo "IMAGE_REPO=${{ env.image_repository_name }}" >> .deploy/.env
123+
echo "RELEASE_VERSION=${{ env.TAG_NAME }}" >> .deploy/.env
124+
125+
# Copy only the docker-compose.yml to remote server home folder
126+
- name: copy files to target server via scp
127+
uses: appleboy/[email protected]
128+
with:
129+
host: ${{ secrets.DEPLOY_HOST }}
130+
username: ${{ secrets.DEPLOY_USERNAME }}
131+
port: 22
132+
key: ${{ secrets.DEPLOY_KEY }}
133+
strip_components: 2
134+
source: "./.deploy/docker-compose.yml,./.deploy/.env"
135+
target: "~/.deploy/${{ github.event.repository.name }}/"
136+
137+
- name: Run remote db migrations
138+
uses: appleboy/[email protected]
139+
env:
140+
APPTOKEN: ${{ secrets.GITHUB_TOKEN }}
141+
USERNAME: ${{ secrets.DEPLOY_USERNAME }}
142+
with:
143+
host: ${{ secrets.DEPLOY_HOST }}
144+
username: ${{ secrets.DEPLOY_USERNAME }}
145+
key: ${{ secrets.DEPLOY_KEY }}
146+
port: 22
147+
envs: APPTOKEN,USERNAME
148+
script: |
149+
set -e
150+
echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin
151+
cd ~/.deploy/${{ github.event.repository.name }}
152+
docker compose pull
153+
export APP_ID=$(docker compose run --entrypoint "id -u" --rm app)
154+
docker compose run --entrypoint "chown $APP_ID:$APP_ID /app/App_Data" --user root --rm app
155+
docker compose up app-migration --exit-code-from app-migration
156+
157+
# Deploy Docker image with your application using `docker compose up` remotely
158+
- name: remote docker-compose up via ssh
159+
uses: appleboy/[email protected]
160+
env:
161+
APPTOKEN: ${{ secrets.GITHUB_TOKEN }}
162+
USERNAME: ${{ secrets.DEPLOY_USERNAME }}
163+
with:
164+
host: ${{ secrets.DEPLOY_HOST }}
165+
username: ${{ secrets.DEPLOY_USERNAME }}
166+
key: ${{ secrets.DEPLOY_KEY }}
167+
port: 22
168+
envs: APPTOKEN,USERNAME
169+
script: |
170+
echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin
171+
cd ~/.deploy/${{ github.event.repository.name }}
172+
docker compose pull
173+
docker compose up app -d

0 commit comments

Comments
 (0)