From a7d333c11016e025db49318ff3d97c95eee13a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20Sz=2E=20Sztup=C3=A1k?= Date: Sat, 27 Jan 2024 21:09:31 +0000 Subject: [PATCH 1/4] Add guidance on how to deploy to Fly.io For more info see https://github.com/CodeYourFuture/curriculum/pull/515 This is splitting out that PR into multiple parts. This is part 4 - Fly.io --- .../content/guides/deployment/flyio/_index.md | 10 ++ .../deployment/flyio/database-access.md | 40 +++++ .../deployment/flyio/serving-frontend.md | 38 +++++ .../guides/deployment/flyio/setup/index.md | 144 ++++++++++++++++++ .../guides/deployment/flyio/setup/signup.png | Bin 0 -> 75446 bytes 5 files changed, 232 insertions(+) create mode 100644 org-cyf/content/guides/deployment/flyio/_index.md create mode 100644 org-cyf/content/guides/deployment/flyio/database-access.md create mode 100644 org-cyf/content/guides/deployment/flyio/serving-frontend.md create mode 100644 org-cyf/content/guides/deployment/flyio/setup/index.md create mode 100644 org-cyf/content/guides/deployment/flyio/setup/signup.png diff --git a/org-cyf/content/guides/deployment/flyio/_index.md b/org-cyf/content/guides/deployment/flyio/_index.md new file mode 100644 index 000000000..1ed8bb513 --- /dev/null +++ b/org-cyf/content/guides/deployment/flyio/_index.md @@ -0,0 +1,10 @@ +--- +emoji: 🚀 +title: Deploying to Fly.io +description: Learn how to deploy your website to Fly.io +weight: 8 +--- + +[Fly.io](https://fly.io/) is a provider allowing you to deploy backend applications that are converted into docker containers. It also allows one to start up a small PostgreSQL database on their system. By making sure that the [frontend is served through the backend]({{< ref "/guides/deployment/flyio/serving-frontend" >}}) you can therefore easily deploy your entire stack on fly.io. + +The main drawback of fly.io that it's free trial only allows you to deploy exactly two systems. For a full stack application this would be the backend (which is also serving the frontend), and the database, meaning you would only be able to deploy a single project freely. diff --git a/org-cyf/content/guides/deployment/flyio/database-access.md b/org-cyf/content/guides/deployment/flyio/database-access.md new file mode 100644 index 000000000..658a37651 --- /dev/null +++ b/org-cyf/content/guides/deployment/flyio/database-access.md @@ -0,0 +1,40 @@ +--- +emoji: 🚀 +title: Accessing fly.io databases +description: Learn how you can access the fly.io PostgreSQL database +weight: 3 +--- + +## Accessing database + +If you have been following the setup guides you would have both a backend and a database system running under fly.io. + +Your database can hold data for multiple applications, so first you need to get a list of them: + +```bash +flyctl postgres db list -a YOURNAME-PROJECTNAME-db +``` + +(Make sure you use your database's name after the `-a` that you have set up before) + +On the list you will find under the NAME column three values: `postgres`, `repmgr` and finally the name of your application's datastore. It will be something like `YOURNAME_PROJECTNAME` - same as your application name but all of the dashes are replaced with underscores. + +Take a note of this name as you will need it later. + +## Uploading database + +To connect to the database you will need to use `flyctl`: + +```bash +flyctl postgres connect -a YOURNAME-PROJECTNAME-db -d YOURNAME_PROJECTNAME +``` + +Where the first value is the name of the database you set up in level 150, and the second value if the datastore name you obtained in the last section. + +The command above will start you up with a proper `psql` console where you can run commands. + +You can also pipe in SQL files. For example if you have an `initdb.sql` file containing SQL commands to initiate a database you can do: + +```bash +flyctl postgres connect -a YOURNAME-PROJECTNAME-db -d YOURNAME_PROJECTNAME < initdb.sql +``` diff --git a/org-cyf/content/guides/deployment/flyio/serving-frontend.md b/org-cyf/content/guides/deployment/flyio/serving-frontend.md new file mode 100644 index 000000000..ea4b47763 --- /dev/null +++ b/org-cyf/content/guides/deployment/flyio/serving-frontend.md @@ -0,0 +1,38 @@ +--- +emoji: 🚀 +title: Serving frontend from your backend +description: Learn how to add support for serving frontend files to Express.JS backends +weight: 2 +--- + +Fly.io doesn't have a built-in CDN for serving static frontend files directly, so if you wish to deploy your frontend you need to do it through your backend. + +Express.JS has fortunately a built-in middleware just to do that. You only need to set the location of your frontend files, and it will take care of serving the contents for you. + +For example if you add the following middleware inside your `/server/app.js`: + +```js +const staticDir = path.join(__dirname, "..", "static"); +app.use(express.static(staticDir)); +``` + +Then anything under the `/static` directory will be served as-is. + +{{}} +Express.JS will not compile these files for you, so if you have Javascript files that need compilation, like React JSX files you need to do that separately. +{{}} + +If you have a React application and you wish it to support React Routes you also need to make sure that every request that doesn't correspond to a real file gets routed to your main website. You can do that with adding a code like the following: + +```js +app.use((req, res, next) => { + if (req.method === "GET" && !req.url.startsWith("/api")) { + return res.sendFile(path.join(staticDir, "index.html")); + } + next(); +}); +``` + +This will point any request that was not yet handled in a previous middleware, and are not starting with `/api` to your `index.html` allowing React Router to handle it internally. + +You can find a full `app.js` example showing static file serving [here](https://github.com/sztupy/Full-Stack-Project-Assessment/blob/main/server/app.js). diff --git a/org-cyf/content/guides/deployment/flyio/setup/index.md b/org-cyf/content/guides/deployment/flyio/setup/index.md new file mode 100644 index 000000000..53ca5fe9f --- /dev/null +++ b/org-cyf/content/guides/deployment/flyio/setup/index.md @@ -0,0 +1,144 @@ +--- +emoji: 🚀 +title: Setup +description: Learn how to set up fly.io +weight: 1 +--- + +## Install `flyctl` + +Fly.io relies on a command line utility to launch and deploy applications. You need to download and install it. You can find installation instructions here: https://fly.io/docs/hands-on/install-flyctl/ + +After installing you might need to close your terminal and reopen it to be able to access `flyctl` + +## Signing up + +To sign up for a fly.io account go to their sign up page at https://fly.io/app/sign-up Make sure you register using the "Sign up using GitHub", as otherwise you won't get added to the Trial package! + +![Sign up using GitHub](signup.png) + +Once signed up you need to log in locally. Type in the following on your terminal then follow the instructions: + +``` +flyctl auth login +``` + +## Application setup + +Once signed up you can now launch your application. Go to the root of your project and type + +``` +flyctl launch +``` + +Once you enter this command it will provide you with a prompt like the following: + +``` +We're about to launch your NodeJS app on Fly.io. Here's what you're getting: + +Organization: test@codeyourfuture.io (fly launch defaults to the personal org) +Name: full-stack-project-assessment (derived from your directory name) +Region: Amsterdam, Netherlands (this is the fastest region for you) +App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM) +Postgres: (not requested) +Redis: (not requested) + +? Do you want to tweak these settings before proceeding? (y/N) +``` + +Make sure you enter `Y` on the prompt as the default settings are not going to use the free tier! + +Once you enter `Y` and press enter you will be redirected to a website where you need to fill in the details as follows: + +- Name: Use `YOURNAME-PROJECTNAME`, example `john-smith-videorec` +- Region: Pick `lhr - London`. +- VM size: Pick `shared-cpu-1x`. Anything else is not included in the free tier! +- VM memory: Pick `256Mb`. Anything else is not included in the free tier! +- Database: `Fly Postgres` +- DB Name: `YOURNAME-PROJECTNAME-db`, example: `john-smith-videorec-db` +- Configuration: `Development - Single Node`. Anything else here is not included in the free tier! +- Redis: `None` + +Once you fill in the details click "Confirm Settings" + +This will set up your database and a machine for running your backend. If everything is successful you should get something like: + +``` +Now: run 'flyctl deploy' to deploy your Node app. +``` + +Once everything is in order you can see that new files have been added by `fly launch` to your repository. These include a `Dockerfile` and a fly settings file called `fly.toml`. Make sure you commit both into your git repository, they will be needed during further deployments! + +## Application deployment + +Finally you are now ready for deployment: + +``` +flyctl deploy +``` + +This command will send your current repository to fly, build a docker image of your code then deploy that image to the Fly.io infrastructure. + +Note: if you are on the Trial Tier the deployer will return an error message saying it could only deploy your app to one machine instead of two because of the Trial Tier limitations: + +``` +Creating a second machine to increase service availability + +Error: error creating a new machine: failed to launch VM: To create more than 1 machine per app please add a payment method. +``` + +This is okay, as one machine is enough for our deployment. Actually if you are not on the Trial Tier, then the deployer will create two machines. If you don't wish to pay for both then you can decrease them to a single one by using the following command: + +``` +flyctl scale count 1 +``` + +If everything else goes well (as mentioned the error message above can be ignored) your application will be available on + +``` +https://YOURNAME-PROJECTNAME.fly.dev +``` + +Make sure to check that it works as expected! + +## Automated Deployments + +Note that fly.io doesn't have access to your GitHub account so it will not deploy your application whenever it changes. Either you need to run `fly deploy` from your computer every time you want to push a change, or you need to set up GitHub to do this for you. + +In order to do this there are two steps: You need to give GitHub access to your fly.io account, and then also need to set up a workflow that runs the deploy command every time you push changes to the `main` branch. + +For the first one you need to run the following command: + +``` +flyctl tokens create deploy -x 999999h +``` + +This will create a token that can be used by flyctl to run deployments. Make sure you save the result as you will need it later. It looks like a very-very long string starting with something like `FlyV1 fm2_lJPECAAAAAAAA...`. + +Next go to your GitHub repository on GitHub, and click Settings. On the left hand side scroll down to "Secrets and variables" and select "Actions". One the page that shows up scroll down to "Repository secrets", and click "New repository secret" + +Set the Name to `FLY_API_TOKEN` and the value to the full results of the previous call. + +Now you have given GitHub access to your fly.io account. You also need to let GitHub know that you want to run a deployment every time your `main` branch changes. First create a file called `.github/workflows/fly-deploy.yml`, with the following content: + +```yaml +name: Fly Deploy +on: + push: + branches: + - main + +jobs: + fly-deploy: + name: Deploy app + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: superfly/flyctl-actions/setup-flyctl@master + - run: flyctl deploy --remote-only + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} +``` + + +Once you commit this file and push it to your `main` branch, GitHub will automatically run `flyctl deploy` against whatever is in the `main` branch. diff --git a/org-cyf/content/guides/deployment/flyio/setup/signup.png b/org-cyf/content/guides/deployment/flyio/setup/signup.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d65118a19c32b58a8c38b651bf07f84a3a44d8 GIT binary patch literal 75446 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 lKZZ@^-*Ndbv6mL|@#@Yf^q89`!!h7T;M1& literal 0 HcmV?d00001 From ab54313d479bde0cb1fe0c161f80126d724efcea Mon Sep 17 00:00:00 2001 From: Dedekind561 Date: Thu, 21 Mar 2024 17:34:23 +0000 Subject: [PATCH 2/4] mv dir for flyio deployment guide --- .../content/guides/deployment-flyio/_index.md | 10 ++++++++ .../database-access.md | 2 +- .../serving-frontend.md | 22 ++++++++---------- .../flyio => deployment-flyio}/setup/index.md | 0 .../setup/signup.png | Bin .../content/guides/deployment/flyio/_index.md | 10 -------- 6 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 org-cyf/content/guides/deployment-flyio/_index.md rename org-cyf/content/guides/{deployment/flyio => deployment-flyio}/database-access.md (98%) rename org-cyf/content/guides/{deployment/flyio => deployment-flyio}/serving-frontend.md (51%) rename org-cyf/content/guides/{deployment/flyio => deployment-flyio}/setup/index.md (100%) rename org-cyf/content/guides/{deployment/flyio => deployment-flyio}/setup/signup.png (100%) delete mode 100644 org-cyf/content/guides/deployment/flyio/_index.md diff --git a/org-cyf/content/guides/deployment-flyio/_index.md b/org-cyf/content/guides/deployment-flyio/_index.md new file mode 100644 index 000000000..72bd0d807 --- /dev/null +++ b/org-cyf/content/guides/deployment-flyio/_index.md @@ -0,0 +1,10 @@ +--- +emoji: 🚀 +title: Deploying to Fly.io +description: Learn how to deploy your website to Fly.io +weight: 8 +--- + +[Fly.io](https://fly.io/) is a provider that allows you to deploy backend applications that are converted into docker containers. It also allows you to start up a small PostgreSQL database on their system. By making sure that the [frontend is served through the backend](../guides/deployment-flyio/serving-frontend) you can easily deploy your entire stack on fly.io. + +The main drawback of fly.io is that its free trial only allows you to deploy exactly two systems. For a full stack application this would be the backend (which is also serving the frontend), and the database, meaning you would only be able to deploy a single project freely. diff --git a/org-cyf/content/guides/deployment/flyio/database-access.md b/org-cyf/content/guides/deployment-flyio/database-access.md similarity index 98% rename from org-cyf/content/guides/deployment/flyio/database-access.md rename to org-cyf/content/guides/deployment-flyio/database-access.md index 658a37651..2ef00e6c7 100644 --- a/org-cyf/content/guides/deployment/flyio/database-access.md +++ b/org-cyf/content/guides/deployment-flyio/database-access.md @@ -5,7 +5,7 @@ description: Learn how you can access the fly.io PostgreSQL database weight: 3 --- -## Accessing database +## 🗝️ Accessing database If you have been following the setup guides you would have both a backend and a database system running under fly.io. diff --git a/org-cyf/content/guides/deployment/flyio/serving-frontend.md b/org-cyf/content/guides/deployment-flyio/serving-frontend.md similarity index 51% rename from org-cyf/content/guides/deployment/flyio/serving-frontend.md rename to org-cyf/content/guides/deployment-flyio/serving-frontend.md index ea4b47763..ee9010729 100644 --- a/org-cyf/content/guides/deployment/flyio/serving-frontend.md +++ b/org-cyf/content/guides/deployment-flyio/serving-frontend.md @@ -6,10 +6,8 @@ weight: 2 --- Fly.io doesn't have a built-in CDN for serving static frontend files directly, so if you wish to deploy your frontend you need to do it through your backend. - -Express.JS has fortunately a built-in middleware just to do that. You only need to set the location of your frontend files, and it will take care of serving the contents for you. - -For example if you add the following middleware inside your `/server/app.js`: +Fortunately, Express.JS has a built-in middleware for serving your frontend through your backend. You only need to set the location of your frontend files, and it will take care of serving the contents for you. +For example, if you add the following middleware inside your `/server/app.js`: ```js const staticDir = path.join(__dirname, "..", "static"); @@ -18,21 +16,21 @@ app.use(express.static(staticDir)); Then anything under the `/static` directory will be served as-is. -{{}} -Express.JS will not compile these files for you, so if you have Javascript files that need compilation, like React JSX files you need to do that separately. +{{}} +Express.JS will not compile these files for you. If you have Javascript files that need compilation, like React JSX files you need to do that separately. {{}} -If you have a React application and you wish it to support React Routes you also need to make sure that every request that doesn't correspond to a real file gets routed to your main website. You can do that with adding a code like the following: +If you have a React application and you wish it to support React Routes you also need to make sure that every request that doesn't correspond to a real file gets routed to your main website. You can do that by adding a code like the following: ```js app.use((req, res, next) => { - if (req.method === "GET" && !req.url.startsWith("/api")) { - return res.sendFile(path.join(staticDir, "index.html")); - } - next(); + if (req.method === "GET" && !req.url.startsWith("/api")) { + return res.sendFile(path.join(staticDir, "index.html")); + } + next(); }); ``` -This will point any request that was not yet handled in a previous middleware, and are not starting with `/api` to your `index.html` allowing React Router to handle it internally. +This will point to any request that was not yet handled in a previous middleware, and does not start with `/api` to your `index.html` allowing React Router to handle it internally. You can find a full `app.js` example showing static file serving [here](https://github.com/sztupy/Full-Stack-Project-Assessment/blob/main/server/app.js). diff --git a/org-cyf/content/guides/deployment/flyio/setup/index.md b/org-cyf/content/guides/deployment-flyio/setup/index.md similarity index 100% rename from org-cyf/content/guides/deployment/flyio/setup/index.md rename to org-cyf/content/guides/deployment-flyio/setup/index.md diff --git a/org-cyf/content/guides/deployment/flyio/setup/signup.png b/org-cyf/content/guides/deployment-flyio/setup/signup.png similarity index 100% rename from org-cyf/content/guides/deployment/flyio/setup/signup.png rename to org-cyf/content/guides/deployment-flyio/setup/signup.png diff --git a/org-cyf/content/guides/deployment/flyio/_index.md b/org-cyf/content/guides/deployment/flyio/_index.md deleted file mode 100644 index 1ed8bb513..000000000 --- a/org-cyf/content/guides/deployment/flyio/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -emoji: 🚀 -title: Deploying to Fly.io -description: Learn how to deploy your website to Fly.io -weight: 8 ---- - -[Fly.io](https://fly.io/) is a provider allowing you to deploy backend applications that are converted into docker containers. It also allows one to start up a small PostgreSQL database on their system. By making sure that the [frontend is served through the backend]({{< ref "/guides/deployment/flyio/serving-frontend" >}}) you can therefore easily deploy your entire stack on fly.io. - -The main drawback of fly.io that it's free trial only allows you to deploy exactly two systems. For a full stack application this would be the backend (which is also serving the frontend), and the database, meaning you would only be able to deploy a single project freely. From 7e661e6b4a3447ba847b6433c803411e6bba3003 Mon Sep 17 00:00:00 2001 From: Dedekind561 Date: Tue, 26 Mar 2024 12:05:18 +0000 Subject: [PATCH 3/4] rm guide --- org-cyf/content/guides/deployment/_index.md | 15 ----- .../guides/deployment/application_stack.md | 56 ------------------- .../deployment/deployment_strategies.md | 42 -------------- .../guides/deployment/free_deployments.md | 44 --------------- .../deployment/from_computers_to_services.md | 46 --------------- 5 files changed, 203 deletions(-) delete mode 100644 org-cyf/content/guides/deployment/_index.md delete mode 100644 org-cyf/content/guides/deployment/application_stack.md delete mode 100644 org-cyf/content/guides/deployment/deployment_strategies.md delete mode 100644 org-cyf/content/guides/deployment/free_deployments.md delete mode 100644 org-cyf/content/guides/deployment/from_computers_to_services.md diff --git a/org-cyf/content/guides/deployment/_index.md b/org-cyf/content/guides/deployment/_index.md deleted file mode 100644 index 993919eca..000000000 --- a/org-cyf/content/guides/deployment/_index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -id: deployment -title: Deploying applications -emoji: 🖥️ ---- - -# What is deployment? - -Deploying in short is making sure the application you are developing is made available for the world wide web to see (or in case for internal systems it is at least visible to the intended audience). Without the ability to deploy the only way to show what you have done is ask others to come to your computer, and show them the work on it in person. - -{{}} -The CYF Curriculum team suggest [Netlify Deployment guide](../deployment-netlify/index.md). as a free provider to use for both your frontend and backend applications, with Supabase as a free database layer for persistence. This is because among the free tier offerings they have the least amount of limitations. - -This suggestion assumes that you are interested in a fully free option, and you can accept the limitations. If you have a deployment budget for your work and/or the limitations are not acceptable you are free to look at other choices as well. -{{}} diff --git a/org-cyf/content/guides/deployment/application_stack.md b/org-cyf/content/guides/deployment/application_stack.md deleted file mode 100644 index fcaaa8d2a..000000000 --- a/org-cyf/content/guides/deployment/application_stack.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: application_stack -title: The application stack -weight: 3 -emoji: 🖥️ ---- - -# The application stack - -In the previous section, we talked about the fact that most full-stack web applications have multiple layers (generally at least a frontend and a backend component and some kind of persistence layer in the form of a database). In this section, we go through these layers and talk about how they are usually deployed. - -## Frontend - -In the curriculum we teach, frontend files are considered static. What this means is that once you deploy them, they will never change until the next deployment comes. Your `index.html`, `index.js` files, your CSS files and your image assets will always contain the same content regardless of how many times they are downloaded and shown in browsers around the world. - -This makes them an ideal candidate to serve them through CDNs, Content Delivery Networks. These systems deploy your static files around the globe allowing people to access them quickly. Serving static files is fairly inexpensive, as you only need disk space and bandwidth, so a lot of companies provide this for free for small projects and you only need to pay once your user count becomes large enough. - -CloudFlare Pages and Netlify are some examples of CDN providers that have generous free tiers allowing you to host your static files with them. GitHub Pages, while not strictly considered a CDN, also allows you to host files from your repository simply. - -{{}} -Do note that while the fact that frontend files are static is true for the frameworks we teach, it is not universally true for all frameworks. Web-focused languages like PHP, and MVC frameworks like Ruby on Rails or Django, allow you to create dynamic frontend files. Some React frameworks also support server-side rendering which can make the frontend part of your application dynamic, and ineligible to be served through CDNs. Anything static, like CSS files or images however you can still serve through CDNs, and it is generally still a good idea to do so to improve your application's loading speed. -{{}} - -CDNs are however not the only way to deploy and serve your frontend. You can also add code to your backend server that will enable it to serve your static files. This allows you to use any of the backend providers (that support persistent deployments) to serve your frontend as well. The main benefit of this approach is that your frontend and backend parts remain closely tied together, you can for example easily deploy them at the same time making sure there are no version discrepancies between the two. However you would lose the benefits of CDNs, and although serving static files are not computationally too involved they still need to be handled, which will in turn slow down your backend. - -## Backend - -In contrast to the frontend, your backend systems are almost always considered dynamic. Every request they receive they need to generate a response on the fly based on the request and usually the contents of the database. This means deploying backends are much more involved, and this shows in free offerings as well - there are fewer options to host your backends for free, and the available ones have hefty limitations, forcing you to pay for an upgrade. - -To deploy your backend there are generally two wide categories of deployment mechanisms: persistent and serverless. - -### Persistent deployment - -In a persistent deployment scenario, you run your backend server all the time. This is very similar to how you usually run your backend during development on your local computer. This allows you to fully utilize the features of your backend framework, including features like backend workers. Latency - the time needed for your server to respond - is usually quite low compared to serverless deployments. - -The biggest drawback is that running a server constantly is more expensive, therefore free tiers are in very limited supply. Glitch, Render and Fly.io are some providers that still offer this, but all of them have strict limitations: the first two will for example stop your server if it doesn't receive activity for a longer time. This will make your service very slow whenever it needs to be restarted. - -Apart from the providers above persistent deployment is what people usually use VPS providers for, and the big players usually have a free tier available. AWS's EC2, Azure's Virtual Machines and Google's Compute Platform all have a one-year time-limited trial that allows you to use their slowest computer for free. However because neither of these services supports automated deployments out of the box, and it is very easy to accidentally overshoot the trial package (for example by accidentally starting up a slightly larger computer than the slowest one) we don't recommend any of these options for the projects we do in the curriculum. - -### Serverless deployment - -Serverless, sometimes called on-demand, functional or lambda deployments means that your server is not running constantly. Whenever there is a request the serverless provider will start up your backend, let it serve the request and then stop it immediately afterward. - -Benefits of this approach are that your service doesn't consume resources unless it's used. It is usually aimed for systems that are either not used constantly, or where there are periods of high usage, which needs more resources to handle. Running your backend on-demand only is also less expensive, so there are more free options available to use. - -The drawback of this approach is that your server is getting started and stopped all the time, so it needs to be developed in a way to make this less of a problem. Unfortunately, Express.JS is not designed with serverless operation in mind, but there is a plugin to allow its core functions to operate. However anything that requires your system to run constantly, including global variables or backend workers will not work and need to be replaced with an alternative. The constant starting and stopping is slow; therefore serverless functions have a higher latency than persistent servers. - -Netlify Functions, Cloudflare Pages, Supabase Functions are some examples that have free tiers available, and big players like AWS's Lambda also provide support for this in their time-limited free tier. - -## Database - -The final part of a full stack application is the persistence layer, which is usually a database, in our current curriculum likely a relational database, like Postgres. - -It's not the only kind of data store that you might need. For example, if your application needs to support file uploads and you need to store those files somewhere you would need to opt in for some kind of file storage server as well, this is outside of the scope of this guide however. - -Free Postgres databases are offered by Supabase, Fly.io and Render with some limitations. Other kinds of SQL databases are available from Cloudflare and are present inside the free tiers of AWS, Azure and GCP. These offerings do not follow Postgres' SQL standard, however, so are outside of the scope of this guide. diff --git a/org-cyf/content/guides/deployment/deployment_strategies.md b/org-cyf/content/guides/deployment/deployment_strategies.md deleted file mode 100644 index 90883add1..000000000 --- a/org-cyf/content/guides/deployment/deployment_strategies.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: deployment_strategies -title: Deployment strategies -weight: 4 -emoji: 🖥️ ---- - -# Deployment strategies - -In the previous section, we went through where you can deploy your application. In this guide, we will go through how to do that. - -## Manual deployment - -Manual deployments are when you need to take action yourself to get your application deployed. This can be anything from running a couple of commands to needing to press a button on a website. - -For example in the scenario where we imagined that you have both a development computer and a small server in your house, deploying your latest version manually could be following the steps below: - -1. Commit your changes and push them to the `main` branch on the development machine -2. Login onto your server computer -3. Go to the running application's directory -4. Stop the application -5. Pull the latest changes from the `main` branch -6. Run `npm i` -7. Start the application again - -Manual deployments done in the way above can be error-prone. For example, if you forget to do step 7 your application will not be running at all at the end. If your provider does not support built-in automated deployments it is usually advised to create scripts that would do all of the steps above, so any time you wish the latest version to be deployed you would just need to run the script. Scripts can fail so you would still need to make sure to check the logs and see if everything went as expected. Trying to access your website after deployment is also a good way to check that everything is in order. - -## Automated deployment - -Automated deployment or Continuous deployment is whenever your code changes, the deployment flow is initiated automatically. For example, any time your code ends up in the `main` branch of your GitHub repository deployment will commence automatically, and your code will end up on your servers. You can imagine this by someone automatically running the deployment script we've created above whenever your repository changes. - -Some frontend and backend providers have GitHub integration and will support automated deployments out of the box. For others, you either need to deploy manually by following a set of steps or automate these manual steps by creating GitHub workflows manually. - -## Pull Request checks - -While automated deployments might sound scary, for example if you make a mistake in your code automated deployments would deploy that mistake automatically. There are ways to mitigate these risks by adding checks that run after each pull request is created, blocking merging if they fail. Common checks include linting, which would make sure that your code adheres to good coding conventions. Other checks that you can include are running unit, integration and end-to-end tests for each PR making sure that the code you've done passes these requirements. - -## Snapshot builds - -Another feature that helps make automated deployments less risky is snapshot builds. These are a feature of some providers where they not only deploy your `main` branch but also deploy your other branches as well in a temporary fashion. For example, if you create a new pull request from branch `new-awesome-feature` the provider will deploy this branch separately to your production environment. This will allow you, and anyone assessing your pull request to check how your changes would look like before actually merging them. - -Not all providers support snapshot builds. For the ones that do not, a common way to mitigate this is to set up two sets of servers, one called _production_ and the other _staging_ (other common names for this second set are _pre-production_ or _sandbox_). You would then deploy to this environment first, and only after checking that it works as expected continue the deployment to _production_ one. Do note that this usually requires you to set up your environment multiple times, and this will also increase the cost of your setup. diff --git a/org-cyf/content/guides/deployment/free_deployments.md b/org-cyf/content/guides/deployment/free_deployments.md deleted file mode 100644 index 066349724..000000000 --- a/org-cyf/content/guides/deployment/free_deployments.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: free_deployments -title: Free deployment offerings -weight: 5 -emoji: 🖥️ ---- - -# Free deployment offerings - -In this section, we will showcase some free options that allow you to deploy your application stack. We will also take note of any limitations each of the providers have. - -{{}} -The CYF Curriculum team suggests Netlify({{< ref "/guides/deployment/netlify" >}}). as a free provider to use for both your frontend and backend applications, with Supabase as a free database layer for persistence. This is because among the free tier offerings they have the least amount of limitations. You will need to check these however to make sure they are still suitable for your use case. - -If the limitations of Netlify and/or Supabase are not suitable, a good alternative is Fly.io. Do note its free tier is limited to two computers, either one backend and one database, or two backends. If you have multiple projects that you want to deploy you'll need to either use a different provider or need to pay for those. -{{}} - -## Frontend - -| Provider | Type | Deployment | Snasphots | Limitations | -| ---------------- | --------------------------------- | --------------------- | ----------------------- | ------------------------------------------------------------------ | -| Netlify | CDN | Automatic from GitHub | Supported, PRs | - | -| Cloudflare Pages | CDN | Automatic from GitHub | Supported, All branches | - | -| Render | CDN or Frontend served by backend | Automatic from GitHub | Supported, PRs | Backend is stopped after inactivity, startup time is slow | -| Fly.io | Frontend served by backend | Manual | None | Free tier limited to two backends, or one backend and one database | - -## Backend - -| Provider | Type | Deployment | Snasphots | Limitations | -| ---------------- | ---------- | --------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| Netlify | Serverless | Automatic from GitHub | Supported, PRs | Requires Express.JS serverless wrapper, does not support all Express.JS features | -| Render | Persistent | Automatic from GitHub | Supported, PRs | Backend is stopped after inactivity, startup time is slow | -| Fly.io | Persistent | Manual | None | Free tier limited to two backends, or one backend and one database. | -| Glitch | Persistent | Manual | None | Backend is stopped after inactivity, startup time is slow. Not designed for production system | -| Cloudflare Pages | Serverless | Automatic from GitHub | Supported, All branches | Does not support Express.JS serverless wrapper at all, backend needs to be written in a different framework, like Hono | - -## Database - -| Provider | Type | Integration | Limitations | -| ------------- | ---------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| Supabase | Relational, PostgreSQL | None | Stops after one week of inactivity | -| Render | Relational, PostgreSQL | Integrates with Render based backends automatically | Gets deleted after 3 months without payment | -| Fly.io | Relational, PostgreSQL | Integrates with fly.io based backends automatically | Free tier limited to one backend and one database. Database can only be accessed from Fly.io backends. | -| Cloudflare D1 | Relational, SQLite | Integrates with Cloudflare Pages automatically | Not following the Postgres SQL standard | diff --git a/org-cyf/content/guides/deployment/from_computers_to_services.md b/org-cyf/content/guides/deployment/from_computers_to_services.md deleted file mode 100644 index 137fc93d4..000000000 --- a/org-cyf/content/guides/deployment/from_computers_to_services.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -id: deployment -title: From your computer to the world -weight: 2 -emoji: 🖥️ ---- - -# From your computer to the world - -While in this curriculum we are mainly going to concentrate on free deployment options to showcase your work you need to understand what "making your work available for the world wide web to see" actually means. In the following section, we will go through a couple of scenarios that are building on each other to show you an idea of why deployments are done in the way they are done. - -## In-person demos - -The easiest, but least convenient way to show your work to others, is to invite them to your computer on which you have your application running. This would assume you know each of your users and trust them enough to give them access to your computer. Also, this assumes your users are happy enough to come to your place physically. - -Unfortunately, neither of these expectations is usually safe to make, so this option is only valid in a limited set of circumstances. Having the ability to demo your work on your computer is useful in classroom settings to show others what you've done and if you have questions. This will remain a useful way during work as well, as pairing with others and demoing them your work is a tried and tested way to showcase what you have done so far to your team. - -## Enabling DMZ on router - -In the previous scenario, the biggest difficulty was that the users had to come to your machine physically in person to look at your website. We can avoid that if we keep your computer connected to your home broadband, and (among other updates that are outside of the scope of this guide) change the firewall settings on your router to allow connections from the outside world to reach your computer (this is usually called the DMZ option). You would then need to keep your machine turned on all the time, and make sure that the work that you have been doing is also running constantly. - -If everything goes well this makes your development computer visible to the world-wide-web and people would be able to see your website. However at any time you need to turn off or restart your machine, or you accidentally stop your running application, maybe to deliver updates to it, they would immediately lose access. Also, they would always see the latest development version you are working on, which might not be something that you want to show the world yet. - -Home broadband systems are also usually not that stable, and if yours is not unlimited you would also need to check the cost of extra bandwidth. Speaking of extra costs, your electricity bill will also increase by keeping your machine running 24/7. And finally, any misconfiguration in your router or computer firewall will enable keen hackers to access your internal network which can usually have serious consequences. - -## Home server - -The next idea is to outsource running your application and have a dedicated computer running your website. You could buy a second machine (this could be an inexpensive one like a Raspberry PI), and set up your router's DMZ settings to point to that instead of your development box. Let's call this second computer a **server** from now on. - -Once you install everything on it as well to mimic your desktop computer this server will then also be able to run your work the same way your development machine can run it. You would need to find a way to copy your work from the development machine to this server (we'll cover some of these in the _manual deployment_ section), but at least you are free to turn off your main computer with the knowledge that your website remains up and visible to your users. - -Because your development machine and server are now running two separate versions of your codebase, you can now also always decide which version you want to run on your server, with the version running on it is usually called **production**. Your database will also be running separately, making it less likely to accidentally update something over there instead of on your development one. - -## Co-location and VPS - -While this is a more valid scenario (and some production systems are running like this), there are still a lot of security and privacy implications of running a server at your own home. The next step would be to use a so-called co-location company, who are specializes in providing a place to store your server machines for a fee. This fee would, among others, cover the electricity, bandwidth and the security of the facility. Once you put your server over there you usually get remote access to your server to allow updates, but generally, they will make sure that the computer is powered on 24/7 and is connected to the internet. - -The next idea is to avoid the big upfront cost of buying a full computer and just rent one. Most hosting companies have an option for you to rent a dedicated server for a monthly price. Depending on budget this still has a hefty price tag, so after a while, you might realize that your project isn't actually that resource intensive and doesn't require a full server. - -That's where VPS (Virtual Private Server) providers come in. They allow you to rent _part_ of a computer running in a virtual machine. There are lots of well-known companies in this sector, like Amazon Web Services, Google Cloud Platform, Microsoft Azure, Digital Ocean, Linode or Ionos just to name a few. You might be able to get access to the slowest computer from these companies completely free on a time-limited trial basis, or they can be as inexpensive as £1-2 a month. - -## As-a-service - -Running your application stack on a VPS is already a good deployment option, but not the only one. In the examples above we have been running the frontend, backend and database part of our application on the same computer. However, these systems actually have different computational needs. Serving static frontend files for example needs bandwidth and disk space but doesn't really require a fast computer or much memory. Databases on the other hand do need a lot of memory to operate efficiently. - -Therefore if you split out your work into separate layers you would be able to deploy each layer on a system that is designed for the needs of that particular layer. For example, the database part can run on a dedicated DaaS (Database-as-a-service) instance, while the frontend can be served on fast CDNs (Content Delivery Networks). There are again plenty of companies providing these services, and fortunately, some have free tiers available to trial them out. Some of these free tiers are generous enough that they can be used to deploy and host simple applications, and we will talk about them in more detail later. From 915ebdf4e52a88dabe09741ea53d35ee1f76d16a Mon Sep 17 00:00:00 2001 From: Dedekind561 Date: Tue, 26 Mar 2024 12:15:09 +0000 Subject: [PATCH 4/4] another general tidy and tweak --- org-cyf/content/guides/deployment-flyio/database-access.md | 4 ++-- org-cyf/content/guides/deployment-flyio/serving-frontend.md | 2 +- org-cyf/content/guides/deployment-flyio/setup/index.md | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/org-cyf/content/guides/deployment-flyio/database-access.md b/org-cyf/content/guides/deployment-flyio/database-access.md index 2ef00e6c7..5ee0fb0c3 100644 --- a/org-cyf/content/guides/deployment-flyio/database-access.md +++ b/org-cyf/content/guides/deployment-flyio/database-access.md @@ -29,11 +29,11 @@ To connect to the database you will need to use `flyctl`: flyctl postgres connect -a YOURNAME-PROJECTNAME-db -d YOURNAME_PROJECTNAME ``` -Where the first value is the name of the database you set up in level 150, and the second value if the datastore name you obtained in the last section. +Where the first value is the name of the database you set up in level 150, and the second value is the datastore name you obtained in the last section. The command above will start you up with a proper `psql` console where you can run commands. -You can also pipe in SQL files. For example if you have an `initdb.sql` file containing SQL commands to initiate a database you can do: +You can also pipe in SQL files. For example, if you have an `initdb.sql` file containing SQL commands to initiate a database you can do: ```bash flyctl postgres connect -a YOURNAME-PROJECTNAME-db -d YOURNAME_PROJECTNAME < initdb.sql diff --git a/org-cyf/content/guides/deployment-flyio/serving-frontend.md b/org-cyf/content/guides/deployment-flyio/serving-frontend.md index ee9010729..516bd497c 100644 --- a/org-cyf/content/guides/deployment-flyio/serving-frontend.md +++ b/org-cyf/content/guides/deployment-flyio/serving-frontend.md @@ -20,7 +20,7 @@ Then anything under the `/static` directory will be served as-is. Express.JS will not compile these files for you. If you have Javascript files that need compilation, like React JSX files you need to do that separately. {{}} -If you have a React application and you wish it to support React Routes you also need to make sure that every request that doesn't correspond to a real file gets routed to your main website. You can do that by adding a code like the following: +If you have a React application and you wish it to support React Routes you also need to make sure that every request that doesn't correspond to a real file gets routed to your main website. You can do that by adding the following code: ```js app.use((req, res, next) => { diff --git a/org-cyf/content/guides/deployment-flyio/setup/index.md b/org-cyf/content/guides/deployment-flyio/setup/index.md index 53ca5fe9f..2af540008 100644 --- a/org-cyf/content/guides/deployment-flyio/setup/index.md +++ b/org-cyf/content/guides/deployment-flyio/setup/index.md @@ -113,9 +113,9 @@ For the first one you need to run the following command: flyctl tokens create deploy -x 999999h ``` -This will create a token that can be used by flyctl to run deployments. Make sure you save the result as you will need it later. It looks like a very-very long string starting with something like `FlyV1 fm2_lJPECAAAAAAAA...`. +This will create a token that can be used by flyctl to run deployments. Make sure you save the result as you will need it later. It looks like a very long string starting with something like `FlyV1 fm2_lJPECAAAAAAAA...`. -Next go to your GitHub repository on GitHub, and click Settings. On the left hand side scroll down to "Secrets and variables" and select "Actions". One the page that shows up scroll down to "Repository secrets", and click "New repository secret" +Next go to your GitHub repository on GitHub, and click Settings. On the left-hand side scroll down to "Secrets and variables" and select "Actions". One the page that shows up scroll down to "Repository secrets", and click "New repository secret" Set the Name to `FLY_API_TOKEN` and the value to the full results of the previous call. @@ -140,5 +140,4 @@ jobs: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} ``` - Once you commit this file and push it to your `main` branch, GitHub will automatically run `flyctl deploy` against whatever is in the `main` branch.