Skip to content

Commit

Permalink
Merge branch 'master' into minor
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Jan 5, 2023
2 parents 7ec8930 + 80da8e0 commit 35dbf82
Show file tree
Hide file tree
Showing 23 changed files with 781 additions and 306 deletions.
11 changes: 11 additions & 0 deletions docs/content/deployment/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: "Deployment"
weight: 2
showtoc: false
---

# Deployment Guides

This section contains guides for deploying a Vendure application to production.

We are planning to publish specific guides for popular platforms soon. For now, you can find platform-specific information in our [Deployment discussion category](https://github.com/vendure-ecommerce/vendure/discussions/categories/deployment).
80 changes: 80 additions & 0 deletions docs/content/deployment/deploying-admin-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "Deploying the Admin UI"
showtoc: true
---

## Deploying the Admin UI

If you have customized the Admin UI with extensions, you should [compile your extensions ahead-of-time as part of the deployment process]({{< relref "/docs/plugins/extending-the-admin-ui" >}}#compiling-as-a-deployment-step).

### Deploying a stand-alone Admin UI

Usually, the Admin UI is served from the Vendure server via the AdminUiPlugin. However, you may wish to deploy the Admin UI app elsewhere. Since it is just a static Angular app, it can be deployed to any static hosting service such as Vercel or Netlify.

Here's an example script that can be run as part of your host's `build` command, which will generate a stand-alone app bundle and configure it to point to your remote server API.

This example is for Vercel, and assumes:

* A `BASE_HREF` environment variable to be set to `/`
* A public (output) directory set to `build/dist`
* A build command set to `npm run build` or `yarn build`
* A package.json like this:
```json
{
"name": "standalone-admin-ui",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "ts-node compile.ts"
},
"devDependencies": {
"@vendure/ui-devkit": "^1.4.5",
"ts-node": "^10.2.1",
"typescript": "~4.3.5"
}
}
```

```TypeScript
// compile.ts
import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
import { DEFAULT_BASE_HREF } from '@vendure/ui-devkit/compiler/constants';
import path from 'path';
import { promises as fs } from 'fs';

/**
* Compiles the Admin UI. If the BASE_HREF is defined, use that.
* Otherwise, go back to the default admin route.
*/
compileUiExtensions({
outputPath: path.join(__dirname, 'build'),
baseHref: process.env.BASE_HREF ?? DEFAULT_BASE_HREF,
extensions: [
/* any UI extensions would go here, or leave empty */
],
})
.compile?.()
.then(() => {
// If building for Vercel deployment, replace the config to make
// api calls to api.example.com instead of localhost.
if (process.env.VERCEL) {
console.log('Overwriting the vendure-ui-config.json for Vercel deployment.');
return fs.writeFile(
path.join(__dirname, 'build', 'dist', 'vendure-ui-config.json'),
JSON.stringify({
apiHost: 'https://api.example.com',
apiPort: '443',
adminApiPath: 'admin-api',
tokenMethod: 'cookie',
defaultLanguage: 'en',
availableLanguages: ['en', 'de'],
hideVendureBranding: false,
hideVersion: false,
}),
);
}
})
.then(() => {
process.exit(0);
});
```
47 changes: 47 additions & 0 deletions docs/content/deployment/getting-data-into-production.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: "Getting data into production"
showtoc: true
weight: 4
---

# Getting data into production

Once you have set up your production deployment, you'll need some way to get your products and other data into the system.

The main tasks will be:

1. Creation of the database schema
2. Importing initial data like roles, tax rates, countries etc.
3. Importing catalog data like products, variants, options, facets
4. Importing other data used by your application

## Creating the database schema

The first item - creation of the schema - can be automatically handled by TypeORM's `synchronize` feature. Switching it on for the initial
run will automatically create the schema. This can be done by using an environment variable:

```TypeScript {hl_lines=[5]}
export const config: VendureConfig = {
// ...
dbConnectionOptions: {
type: 'postgres',
synchronize: process.env.DB_SYNCHRONIZE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
},
// ...
};
```

Set the `DB_SYNCHRONIZE` variable to `true` on first start, and then after the schema is created, set it to `false`.

## Importing initial & catalog data

Importing initial and catalog data can be handled by Vendure `populate()` helper function - see the [Importing Product Data guide]({{< relref "importing-product-data" >}}).

## Importing other data

Any kinds of data not covered by the `populate()` function can be imported using a custom script, which can use any Vendure service or service defined by your custom plugins to populate data in any way you like. See the [Stand-alone scripts guide]({{< relref "stand-alone-scripts" >}}).
72 changes: 72 additions & 0 deletions docs/content/deployment/horizontal-scaling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: "Horizontal scaling"
showtoc: true
weight: 2
---

# Horizontal scaling

"Horizontal scaling" refers to increasing the performance capacity of your application by running multiple instances.

This type of scaling has two main advantages:

1. It can enable increased throughput (requests/second) by distributing the incoming requests between multiple instances.
2. It can increase resilience because if a single instance fails, the other instances will still be able to service requests.

As discussed in the [Server resource requirements guide]({{< relref "server-resource-requirements" >}}), horizontal scaling can be the most cost-effective way of deploying your Vendure server due to the single-threaded nature of Node.js.

In Vendure, both the server and the worker can be scaled horizontally. Scaling the server will increase the throughput of the GraphQL APIs, whereas scaling the worker can increase the speed with which the job queue is processed by allowing more jobs to be run in parallel.

## Multi-instance configuration

In order to run Vendure in a multi-instance configuration, there are some important configuration changes you'll need to make. The key consideration in configuring Vendure for this scenario is to ensure that any persistent state is managed externally from the Node process, and is shared by all instances. Namely:

* The JobQueue should be stored externally using the [DefaultJobQueuePlugin]({{< relref "default-job-queue-plugin" >}}) (which stores jobs in the database) or the [BullMQJobQueuePlugin]({{< relref "bull-mqjob-queue-plugin" >}}) (which stores jobs in Redis), or some other custom JobQueueStrategy. **Note:** the BullMQJobQueuePlugin is much more efficient than the DefaultJobQueuePlugin, and is recommended for production applications.
* A custom [SessionCacheStrategy]({{< relref "session-cache-strategy" >}}) must be used which stores the session cache externally (such as in the database or Redis), since the default strategy stores the cache in-memory and will cause inconsistencies in multi-instance setups. [Example Redis-based SessionCacheStrategy]({{< relref "session-cache-strategy" >}})
* When using cookies to manage sessions, make sure all instances are using the _same_ cookie secret:
```TypeScript
const config: VendureConfig = {
authOptions: {
cookieOptions: {
secret: 'some-secret'
}
}
}
```
* Channel and Zone data gets cached in-memory as this data is used in virtually every request. The cache time-to-live defaults to 30 seconds, which is probably fine for most cases, but it can be configured in the [EntityOptions]({{< relref "entity-options" >}}#channelcachettl).

## Using Docker or Kubernetes

One way of implementing horizontal scaling is to use Docker to wrap your Vendure server & worker in a container, which can then be run as multiple instances.

Some hosting providers allow you to provide a Docker image and will then run multiple instances of that image. Kubernetes can also be used to manage multiple instances
of a Docker image.

For a more complete guide, see the [Using Docker guide]({{< relref "using-docker" >}}).

## Using PM2

[PM2](https://pm2.keymetrics.io/) is a process manager which will spawn multiple instances of your server or worker, as well as re-starting any instances that crash. PM2 can be used on VPS hosts to manage multiple instances of Vendure without needing Docker or Kubernetes.

PM2 must be installed on your server:

```sh
npm install pm2@latest -g
```

Your processes can then be run in [cluster mode](https://pm2.keymetrics.io/docs/usage/cluster-mode/) with the following command:

```sh
pm2 start ./dist/index.js -i 4
```

The above command will start a cluster of 4 instances. You can also instruct PM2 to use the maximum number of available CPUs with `-i max`.

Note that if you are using pm2 inside a Docker container, you should use the `pm2-runtime` command:

```Dockerfile
# ... your existing Dockerfile config
RUN npm install pm2 -g
CMD ["pm2-runtime", "app.js", "-i", "max"]
```
Binary file not shown.
116 changes: 116 additions & 0 deletions docs/content/deployment/production-configuration/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: 'Production configuration'
showtoc: true
weight: 0
---

# Production configuration

This is a guide to the recommended configuration for a production Vendure application.

## Environment variables

Keep sensitive information or context-dependent settings in environment variables. In local development you can store the values in a `.env` file. For production, you should use the mechanism provided by your hosting platform to set the values for production.

The default `@vendure/create` project scaffold makes use of environment variables already. For example:

```TypeScript
const IS_DEV = process.env.APP_ENV === 'dev';
```

The `APP_ENV` environment variable can then be set using the admin dashboard of your hosting provider:

{{< figure src="./env-var-ui.webp" title="A typical UI for setting env vars" >}}

If you are using [Docker or Kubernetes]({{< relref "using-docker" >}}), they include their own methods of setting environment variables.

## Superadmin credentials

Ensure you set the superadmin credentials to something other than the default of `superadmin:superadmin`. Use your hosting platform's environment variables to set a **strong** password for the Superadmin account.

```TypeScript
import { VendureConfig } from '@vendure/core';

export const config: VendureConfig = {
authOptions: {
tokenMethod: ['bearer', 'cookie'],
superadminCredentials: {
identifier: process.env.SUPERADMIN_USERNAME,
password: process.env.SUPERADMIN_PASSWORD,
},
},
// ...
};
```

## API hardening

It is recommended that you install and configure the [HardenPlugin]({{< relref "harden-plugin" >}}) for all production deployments. This plugin locks down your schema (disabling introspection and field suggestions) and protects your Shop API against malicious queries that could otherwise overwhelm your server.

Install the plugin:

```sh
npm install @vendure/harden-plugin

# or

yarn add @vendure/harden-plugin
```

Then add it to your VendureConfig:

```TypeScript
import { VendureConfig } from '@vendure/core';
import { HardenPlugin } from '@vendure/harden-plugin';

const IS_DEV = process.env.APP_ENV === 'dev';

export const config: VendureConfig = {
// ...
plugins: [
HardenPlugin.init({
maxQueryComplexity: 500,
apiMode: IS_DEV ? 'dev' : 'prod',
}),
// ...
]
};
```

{{< alert primary >}}
For a detailed explanation of how to best configure this plugin, see the [HardenPlugin docs]({{< relref "harden-plugin" >}}).
{{< /alert >}}

## ID Strategy

By default, Vendure uses auto-increment integer IDs as entity primary keys. While easier to work with in development, sequential primary keys can leak information such as the number of orders or customers in the system.

For this reason you should consider using the UuidIdStrategy for production.

```TypeScript
import { UuidIdStrategy, VendureConfig } from '@vendure/core';

export const config: VendureConfig = {
entityIdStrategy: new UuidIdStrategy(),
// ...
}
```

Another option, if you wish to stick with integer IDs, is to create a custom [EntityIdStrategy]({{< relref "entity-id-strategy" >}}) which uses the `encodeId()` and `decodeId()` methods to obfuscate the sequential nature of the ID.

## Database Timezone

Vendure internally treats all dates & times as UTC. However, you may sometimes run into issues where dates are offset by some fixed amount of hours. E.g. you place an order at 17:00, but it shows up in the Admin UI as being placed at 19:00. Typically, this is caused by the timezone of your database not being set to UTC.

You can check the timezone in **MySQL/MariaDB** by executing:

```SQL
SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);
```
and you should expect to see `00:00:00`.

In **Postgres**, you can execute:
```SQL
show timezone;
```
and you should expect to see `UTC` or `Etc/UTC`.
29 changes: 29 additions & 0 deletions docs/content/deployment/server-resource-requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: 'Server resource requirements'
showtoc: true
weight: 1
---

## Server resource requirements

### RAM

The Vendure server and worker process each use around 200-300MB of RAM when idle. This figure will increase under load.

The total RAM required by a single instance of the server depends on your project size (the number of products, variants, customers, orders etc.) as well as expected load (the number of concurrent users you expect). As a rule, 512MB per process would be a practical minimum for a smaller project with low expected load.

### CPU

CPU resources are generally measured in "cores" or "vCPUs" (virtual CPUs) depending on the type of hosting. The exact relationship between vCPUs and physical CPU cores is out of the scope of this guide, but for our purposes we will use "CPU" to refer to both physical and virtual CPU resources.

Because Node.js is single-threaded, a single instance of the Vendure server or worker will not be able to take advantage of multiple CPUs. For example, if you set up a server instance running with 4 CPUs, the server will only use 1 of those CPUs and the other 3 will be wasted.

Therefore, when looking to optimize performance (for example, the number of requests that can be serviced per second), it makes sense to scale horizontally by running multiple instances of the Vendure server. See the [Horizontal Scaling guide]({{< relref "horizontal-scaling" >}}).

## Load testing

It is important to test whether your current server configuration will be able to handle the loads you expect when you go into production. There are numerous tools out there to help you load test your application, such as:

- [k6](https://k6.io/)
- [Artillery](https://www.artillery.io/)
- [jMeter](https://jmeter.apache.org/)
Loading

0 comments on commit 35dbf82

Please sign in to comment.