|
| 1 | +--- |
| 2 | +title: 'AI Chat - A Customizable, Private, ChatGPT-like UI' |
| 3 | +excerpt: 'Configuring your GitHub repo for SSH and CDN deployments' |
| 4 | +coverImage: '/assets/blog/dynamic-routing/cover.jpg' |
| 5 | +date: '2021-11-09T00:00:00.000Z' |
| 6 | +author: |
| 7 | + name: Author |
| 8 | + picture: '/assets/blog/authors/author1.svg' |
| 9 | +ogImage: |
| 10 | + url: '/assets/blog/dynamic-routing/cover.jpg' |
| 11 | +--- |
| 12 | + |
| 13 | +# ServiceStack GitHub Action Deployments |
| 14 | + |
| 15 | +The [release.yml](https://github.com/NetCoreTemplates/nextjs/blob/main/.github/workflows/release.yml) |
| 16 | +in this template enables GitHub Actions CI deployment to a dedicated server with SSH access. |
| 17 | + |
| 18 | +## Overview |
| 19 | +`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. |
| 20 | + |
| 21 | +GitHub Actions specified in `release.yml` then copy files remotely via scp and use `docker-compose` to run the app remotely via SSH. |
| 22 | + |
| 23 | +## What's the process of `release.yml`? |
| 24 | + |
| 25 | + |
| 26 | + |
| 27 | +## Deployment server setup |
| 28 | +To get this working, a server needs to be setup with the following: |
| 29 | + |
| 30 | +- SSH access |
| 31 | +- docker |
| 32 | +- docker-compose |
| 33 | +- ports 443 and 80 for web access of your hosted application |
| 34 | + |
| 35 | +This can be your own server or any cloud hosted server like Digital Ocean, AWS, Azure etc. |
| 36 | + |
| 37 | +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. |
| 38 | + |
| 39 | +To let your server handle multiple ServiceStack applications and automate the generation and management of TLS certificates, an additional docker-compose file is provided in this template, `nginx-proxy-compose.yml`. This docker-compose file is ready to run and can be copied to the deployment server. |
| 40 | + |
| 41 | +For example, once copied to remote `~/nginx-proxy-compose.yml`, the following command can be run on the remote server. |
| 42 | + |
| 43 | +``` |
| 44 | +docker-compose -f ~/nginx-proxy-compose.yml up -d |
| 45 | +``` |
| 46 | + |
| 47 | +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. |
| 48 | + |
| 49 | +### GitHub Actions secrets |
| 50 | + |
| 51 | +The `release.yml` uses the following secrets. |
| 52 | + |
| 53 | +| Required Secrets | Description | |
| 54 | +| -- | -- | |
| 55 | +| `DEPLOY_API` | Hostname used to SSH deploy .NET App to, this can either be an IP address or subdomain with A record pointing to the server | |
| 56 | +| `DEPLOY_USERNAME` | Username to log in with via SSH e.g, **ubuntu**, **ec2-user**, **root** | |
| 57 | +| `DEPLOY_KEY` | SSH private key used to remotely access deploy .NET App | |
| 58 | +| `LETSENCRYPT_EMAIL` | Email required for Let's Encrypt automated TLS certificates | |
| 59 | + |
| 60 | +To also enable deploying static assets to a CDN: |
| 61 | + |
| 62 | +| Optional Secrets | Description | |
| 63 | +| -- | -- | |
| 64 | +| `DEPLOY_CDN` | Hostname where static **/wwwroot** assets should be deployed to | |
| 65 | + |
| 66 | +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. |
| 67 | + |
| 68 | +```bash |
| 69 | +gh secret set DEPLOY_API -b"<DEPLOY_API>" |
| 70 | +gh secret set DEPLOY_USERNAME -b"<DEPLOY_USERNAME>" |
| 71 | +gh secret set DEPLOY_KEY < key.pem # DEPLOY_KEY |
| 72 | +gh secret set LETSENCRYPT_EMAIL -b"<LETSENCRYPT_EMAIL>" |
| 73 | +gh secret set DEPLOY_CDN -b"<DEPLOY_CDN>" |
| 74 | +``` |
| 75 | + |
| 76 | +These secrets are used to populate variables within GitHub Actions and other configuration files. |
| 77 | + |
| 78 | +## UI Deployment |
| 79 | + |
| 80 | +The Next.js `ui` application is built and deployed to GitHub Pages during the `release.yml` workflow process by committing the result of `npm run build` to `gh-pages` branch in the repository. |
| 81 | + |
| 82 | +Variable replacement of `$DEPLOY_API` and `$DEPLOY_CDN` is performed on the following files as a way to coordinate configuration between the `ui` and `api` project. |
| 83 | + |
| 84 | +- `ui/next.config.js` - Set backend .NET API URL for UI App to use |
| 85 | +- `ui/post.build.js` - If exists, run from GitHub Action after `npm run build` |
| 86 | + |
| 87 | +### post.build.js |
| 88 | + |
| 89 | +The `post.build.js` script helps when also publishing `/ui` assets to CDN by first copying the generated |
| 90 | +`index.html` home page into `404.html` in order to enable full page reloads to use SPA client routing: |
| 91 | + |
| 92 | +```js |
| 93 | +const fs = require("fs") |
| 94 | +const path = require("path") |
| 95 | + |
| 96 | +// Replaced in release.yml with GitHub Actions secrets |
| 97 | +const DEPLOY_API = 'https://$DEPLOY_API' |
| 98 | +const DEPLOY_CDN = 'https://$DEPLOY_CDN' |
| 99 | + |
| 100 | +const DIST = '../api/Jamstacks/wwwroot' |
| 101 | + |
| 102 | +// 404.html SPA fallback (supported by GitHub Pages, Cloudflare & Netlify CDNs) |
| 103 | +fs.copyFileSync( |
| 104 | + path.resolve(`${DIST}/index.html`), |
| 105 | + path.resolve(`${DIST}/404.html`)) |
| 106 | + |
| 107 | +// Define Virtual Host for GitHub Pages CDN |
| 108 | +fs.writeFileSync(`${DIST}/CNAME`, DEPLOY_CDN) |
| 109 | + |
| 110 | +// Define /api proxy routes (supported by Cloudflare or Netlify CDNs) |
| 111 | +fs.writeFileSync(`${DIST}/_redirects`, |
| 112 | + fs.readFileSync(`${DIST}/_redirects`, 'utf-8') |
| 113 | + .replace(/{DEPLOY_API}/g, DEPLOY_API)) |
| 114 | +``` |
| 115 | + |
| 116 | +Whilst the `_redirects` file is a convention supported by many [popular Jamstack CDNs](https://jamstack.wtf/#deployment) |
| 117 | +that sets up a new rule that proxies `/api*` requests to where the production .NET App is deployed to in order |
| 118 | +for API requests to not need CORS: |
| 119 | + |
| 120 | +``` |
| 121 | +/api/* {DEPLOY_API}/api/:splat 200 |
| 122 | +``` |
| 123 | + |
| 124 | +By default this template doesn't use the `/api` proxy route & makes CORS API requests so it can be freely hosted |
| 125 | +on GitHub pages CDN. |
| 126 | + |
| 127 | +## Pushing updates and rollbacks |
| 128 | + |
| 129 | +By default, deployments of both the `ui` and `api` occur on commit to your main branch. A new Docker image for your ServiceStack API is produced, pushed to GHCR.io and hosted on your Linux server with Docker Compose. |
| 130 | +Your React UI is built and pushed to the repository GitHub Pages. |
| 131 | + |
| 132 | +The template also will run the release process on the creation of a GitHub Release making it easier to switch to manual production releases. |
| 133 | + |
| 134 | +Additionally, the `release.yml` workflow can be run manually specifying a version. This enables production rollbacks based on previously tagged releases. |
| 135 | +A release must have already been created for the rollback build to work, it doesn't create a new Docker build based on previous code state, only redeploys as existing Docker image. |
| 136 | + |
| 137 | +## No CORS Hosting Options |
| 138 | + |
| 139 | +The `CorsFeature` needs to be enabled when adopting our recommended deployment configuration of having static |
| 140 | +`/wwwroot` assets hosted from a CDN in order to make cross-domain requests to your .NET APIs. |
| 141 | + |
| 142 | +### Using a CDN Proxy |
| 143 | +Should you want to, our recommended approach to avoid your App making CORS requests is to define an `/api` proxy route |
| 144 | +on your CDN to your `$DEPLOY_API` server. |
| 145 | + |
| 146 | +To better support this use-case, this template includes populating the `_redirects` file used by popular CDNs like |
| 147 | +[Cloudflare proxy redirects](https://developers.cloudflare.com/pages/platform/redirects) and |
| 148 | +[Netlify proxies](https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service) to define |
| 149 | +redirect and proxy rules. For AWS CloudFront you would need to define a |
| 150 | +[Behavior for a custom origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html). |
| 151 | + |
| 152 | +### No CDN |
| 153 | + |
| 154 | +Of course the easiest solution is to not need CORS in the first place by not deploying to a CDN and serving both `/api` |
| 155 | +and UI from your .NET App. But this would forgo all the performance & UX benefits that has made |
| 156 | +[Jamstack](https://jamstack.org) approach so popular. |
0 commit comments