In this article, we will create an application using the Shopify CLI on a VPS. We’ll start by choosing the hosting configuration and finish with deploying it on a live store. Naturally, you will need access to a Shopify Partner account for app development, as well as full access to an account with a working store and a dev store.

STEP 1: Creating a VPS on the hosting service

Before creating a VPS in the cloud, you need to understand which configuration will suit your application. In our case, we’ll use the cheapest option since this is just an example. However, for a serious application, you would need at least 2 CPUs, 4 GB of RAM, and 40 GB SSD.

Out of the box, Beget offers to install Ubuntu 22.04. This option works fine for us. So, we create a VPS server with the default settings provided by the service.

Once the virtual server is created, the hosting service will send the SSH access password to your email.

STEP 2: Creating a domain and linking it to the VPS

2.1 Creating a third-level domain

Go to the hosting control panel, in this case, Beget, to the “Domain and Subdomain Management” section.

Create a new domain. I already have a second-level domain, wtemu.ru, so I will create a third-level domain based on it:

shop-app.wtemu.ru

Link it to the VPS server you created. To do this, use the “Add Domains” button. After that, the third-level domain will be connected to our VPS.

2.2 Checking DNS linkage

You can verify the correct linking via “DNS Records Management.”

In the dropdown menu, select the parent domain (wtemu.ru).

Scroll down to the subzones and DNS records.

Here you’ll see our subdomain shop-app.wtemu.ru with four records: A, MX, MX, and TXT.

For our purposes, we only need the A record; the rest can be ignored.

2.3 Checking domain resolution on the VPS

Next, check that the domain correctly resolves to the VPS IP.

Connect to the VPS via SSH as root.

Run the command:

ping shop-app.wtemu.ru

If everything is set up correctly, you should see something like this:

root@spurhanwfx:/# ping shop-app.wtemu.ru
PING shop-app.wtemu.ru (31.129.111.75) 56(84) bytes of data.
64 bytes from 31.129.111.75: icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from 31.129.111.75: icmp_seq=2 ttl=64 time=0.031 ms

This confirms that the domain is successfully linked to the VPS and correctly resolves to the IP.

STEP 3: Creating a SWAP file to prevent VPS crashes

3.1 Why SWAP is needed

Since we created a VPS with a minimal configuration, under heavy load, OOM (Out of Memory) kills can occur — when the system runs out of RAM. To prevent this, we create a swap file. This ensures stable operation of a low-spec virtual server.

3.2 Checking existing swap

First, check whether swap is enabled by default:

free -h

If all values in the Swap row are zero, swap is absent, and it is safe to create your own file.

3.3 Creating a swap file

We’ll create a 2 GB swap file. The fastest way is using fallocate:

fallocate -l 2G /swapfile

The file will be located at the system root and used as swap.

On some file systems, fallocate may not work. In that case, use:

dd if=/dev/zero of=/swapfile bs=1M count=2048

3.4 Setting file permissions

The swap file must be accessible only to the root user; otherwise, the system will reject it:

chmod 600 /swapfile
ls -lh /swapfile

If everything is correct, you should see something like:

-rw------- 1 root root 2.0G /swapfile

3.5 Activating the swap file

Now we make the file active as swap:

mkswap /swapfile
swapon /swapfile

Check the connection:

free -h

If you see 2.0G in the output, the swap has been successfully activated.

3.6 Auto-mounting swap after reboot

By default, swap is disabled after a VPS reboot. To enable automatic activation, edit the fstab file:

nano /etc/fstab

Add the following at the end:

/swapfile none swap sw 0 0

Save the file: Ctrl + O → Enter → Ctrl + X.

Now the swap will be activated every time the system starts.

3.7 Adjusting swappiness

Ubuntu uses swap aggressively by default, which isn’t always ideal for a VPS. Check the current value:

cat /proc/sys/vm/swappiness

It’s usually 60. For a server, it’s better to set it to 10:

sysctl vm.swappiness=10

To make this value persistent after reboot, add it to the config:

nano /etc/sysctl.conf

Add at the end:

vm.swappiness=10

Save: Ctrl + O → Enter → Ctrl + X.

We have created a 2 GB swap file that:

  • works immediately,
  • auto-mounts after reboot,
  • ensures stable VPS operation under low RAM conditions.

If necessary, the swap can be increased or removed.

STEP 4: Preparing the server for proper Shopify CLI operation on the VPS

4.1 System update

Assuming you are already logged into the VPS via SSH as the root user. First, update the system:

apt update && apt upgrade -y

This ensures that all packages are up to date and the system is ready for further configuration.

4.2 Installing Node.js via the official repository

Next, we need to set up a proper Node.js environment for Shopify CLI and the future production application. Do not use the Node.js from apt, as it contains outdated versions. Instead, use the official NodeSource repository and install Node.js LTS (20.x):

curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt install -y nodejs

Check the Node.js and npm versions:

node -v
npm -v

Update npm to the latest version:

npm install -g npm@latest
npm -v

4.3 Installing Git and preparing the infrastructure

Git is required to manage the project and clone repositories:

apt install -y git
git --version

Next, install Nginx and PM2 to manage the application and prepare the server for the production domain shop-app.wtemu.ru:

apt install nginx -y
systemctl status nginx

If everything is correct, the status will show active (running).
After that, when you visit http://<server_ip> in a browser, you should see the default “Welcome to nginx” page. This means Nginx is working correctly. Full Nginx configuration and SSL certificate setup will be done later at the HTTPS preparation stage.

4.4 Installing PM2 and setting up autostart

PM2 ensures the application doesn’t crash and restarts automatically. Install it globally:

npm install -g pm2
pm2 -v

Set up autostart so PM2 processes restart after a reboot:

pm2 startup
pm2 save

4.5 Installing build-essential

To compile Node.js dependencies and future project modules, install build-essential:

apt install -y build-essential

Now the VPS is fully prepared:

  • the system is updated,
  • the latest version of Node.js is installed,
  • npm is configured,
  • Git is installed,
  • Nginx is deployed,
  • PM2 is installed with autostart,
  • system tools for building dependencies are added.

The server is ready for deploying a Shopify application in both dev and production modes.

STEP 5: Running the application in dev mode

5.1 Preparation and understanding the process

Our task is to prepare Shopify to work with an application that will run on our production domain shop-app.wtemu.ru. At this stage, we are not touching the VPS or the code yet, focusing instead on Shopify and key development decisions.

It’s important to understand that in the future, the application (in production) will live on the server, have a domain, and be installed in a Shopify store. For this, it is first created in the Shopify Partner Account in development mode, regardless of whether it will be public or private.

Any app is created in the Partner Account on a test DevStore. After development is complete, the finished app is built for the production store. A production store only accepts production-ready code. No app will work in a live store in development mode. That’s why the DevStore exists — a store for app development and testing.

In our case, we have two stores: one for development and one live. Therefore, we will refer to development in the DevStore and production in the live store.

Another important detail concerns scopes: for private apps, most scopes are available immediately, but for public apps, many scopes require Shopify approval.

5.2 Creating the application using Shopify CLI on the VPS

We’ll create the application using Shopify CLI as root. First, install the necessary utilities for proper browser authentication:

apt-get install xdg-utils
cd /var/www/
npm init @shopify/app@latest

The first command installs authentication utilities, and the second initializes the Shopify CLI environment and the starter app template. A link for authentication will appear in the terminal: copy it, open it in a browser, log in, confirm the creation of the app. The CLI will remember the server token and continue the installation, offering options:

? Get started building your app:
Build a React Router app (recommended)

? For your React Router template, which language do you want?
JavaScript

? We recommend installing Shopify CLI globally in your system. Would you like to install it now?
Yes

? Which organization is this work for?
Partner Company Ltd

? Create this project as a new app on Shopify?
Yes, create it as a new app

? App name:
shop-app

After installation is complete, a new app will appear in the DevStore dashboard.

5.3 Setting up Custom Distribution and running in dev mode

In the DevStore developer dashboard, select the Home tab, then in the right column choose Select Distribution Method.

Next, select Custom Distribution and confirm. A page with a Generate Link button will appear, which can be closed — at this stage, the link is not needed. Using Custom Distribution allows the app to access almost all API scopes for Shopify.

Return to the terminal and run:

cd /var/www/shop-app
shopify app dev

This command runs the application in development mode on the VPS, making it accessible in the DevStore.

To open the app, go to:

devstore > settings > apps > shop-app > open

The first time, the app must be opened from DevStore → Settings → Apps. If everything worked correctly, the starter application will open.

STEP 6: Setting up SSL certificates and Nginx

Now we need to create SSL certificates using Let’s Encrypt with the CertBot module. Certificates are required for HTTPS. Without HTTPS, the Shopify application will not work in production.

apt install certbot python3-certbot-nginx -y
certbot --nginx -d shop-app.wtemu.ru

Next, create a new Nginx configuration for the application:

nano /etc/nginx/sites-available/shop-app

Enter exactly the following content:

server {
 listen 80;
 server_name shop-app.wtemu.ru;
 return 301 https://$host$request_uri;
}

server {
 listen 443 ssl;
 server_name shop-app.wtemu.ru;

 ssl_certificate /etc/letsencrypt/live/shop-app.wtemu.ru/fullchain.pem;
 ssl_certificate_key /etc/letsencrypt/live/shop-app.wtemu.ru/privkey.pem;

 location / {
   proxy_pass http://localhost:3000;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
 }
}

Enable the configuration:

ln -s /etc/nginx/sites-available/shop-app /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

If you encounter problems, possible causes include:

  • Ensure that the certificates exist at the paths specified in the config.
  • Check that the config is present in /etc/nginx/sites-enabled/.

Verification:

systemctl status nginx

Open your browser:

https://shop-app.wtemu.ru

If you see 502 Bad Gateway, everything is fine! This indicates that Nginx is configured correctly and the SSL certificates are applied to the domain, in our case shop-app.wtemu.ru.

If you see the default Nginx welcome page, this is likely a configuration conflict. One common cause is duplication — for example, if the domain was mistakenly listed in multiple configs in /etc/nginx/sites-enabled/.

STEP 7: Building the application using Shopify CLI on the VPS in Production

7.1 Preparing the production application

Now that our domain is ready, we move on to setting up the application for production. It’s important to understand that you cannot run an app in development mode on a live store. At the same time, in the DevStore, you can freely use both build and dev modes. Although the process is generally the same, there is one key difference: for a live store, you need to create a new entry point (app), whereas for the DevStore, you can use the same app for development and testing builds.

To create a production version of our starter app and run it on the live store, we need to obtain an entry point for that store. In other words, in the developer admin panel of the live store, you must create a new app, get the client ID and secret, specify scopes, and enter the correct URLs:

  • App URLhttps://shop-app.wtemu.ru
  • Scopeswrite_products
  • Redirect URLs["https://shop-app.wtemu.ru/api/auth"]

Click the Release button — this will release the application version.

7.2 Configuring the environment and connecting to the code

To access the application via SSH and edit the code, use VSCode with the SSH Remote extension. Connect and open the project.

Open the file shopify.app.toml and adjust the lines:

client_id = "your_obtained_client_ID"
name = "shop-app"
application_url = "https://shop-app.wtemu.ru"
automatically_update_urls_on_dev = false
scopes = "write_products"
redirect_urls = ["https://shop-app.wtemu.ru/api/auth"]

In development mode, the app takes variables from the core, while in production it uses the .env file. Create a .env file in the project root with the minimal set of constants:

SHOPIFY_API_KEY=your_api_key_for_live_store
SHOPIFY_API_SECRET=your_api_secret_for_live_store
SCOPES=write_products
SHOPIFY_APP_URL=https://shop-app.wtemu.ru

Ensure that in package.json the start script is set for production:

"start": "react-router-serve ./build/server/index.js"

Next, in the app folder, open shopify.server.js and at the top add:

import 'dotenv/config';

This line will load constants from the .env file.

To remove any possible Vite cache issues, run:

rm -rf node_modules/.vite-temp
rm -rf node_modules/.cache

Reinstall dependencies:

npm install

7.3 Deploying and testing the build created via Shopify CLI on the VPS

To ensure everything is set up correctly, run:

shopify app deploy

This will rebuild and reconnect the latest version of the app. After that, run the build — there should be no errors:

npm run build

Now start the app using PM2:

pm2 start npm --name shop-app -- run start
pm2 save
pm2 logs

If everything is successful, the logs will be green.

Next, test the application on the live store. Open your browser and go to https://shop-app.wtemu.ru. If everything is correct, a page will appear prompting you to enter the live store domain and log in. You will be redirected to the Shopify authentication page.

After logging in, you will reach the store page. If your app has not been installed via the admin panel, it will not appear in the store’s sidebar. To activate it, install it and open the app via:

Settings → Apps → shop-app

Click the three dots next to the app in the list and select Open app. After that, your application will start in the live store!