{"id":1855,"date":"2025-07-01T11:37:23","date_gmt":"2025-07-01T08:37:23","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/deploying-a-node-js-application-to-upcloud-from-jenkins\/"},"modified":"2025-07-01T11:37:23","modified_gmt":"2025-07-01T08:37:23","slug":"deploying-a-node-js-application-to-upcloud-from-jenkins","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/deploying-a-node-js-application-to-upcloud-from-jenkins\/","title":{"rendered":"Deploying a Node.js Application to UpCloud From Jenkins"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.jenkins.io\/\" target=\"_blank\" rel=\"noopener\">Jenkins<\/a> is one of the most popular open-source automation servers for setting up CI\/CD pipelines, and when paired with <a href=\"https:\/\/upcloud.com\/global\/\">UpCloud<\/a>, it becomes a powerful platform for deploying Node.js applications. Jenkins gives you full control over your build and deployment processes, making it ideal for teams that want a highly customizable pipeline. Combined with UpCloud\u2019s high-performance virtual machines and predictable pricing, it\u2019s a solid choice for developers looking to self-host and scale their infrastructure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this tutorial, you\u2019ll learn how to set up a self-hosted Jenkins server on UpCloud and use it to automatically deploy a Node.js application from GitHub to another UpCloud server. We\u2019ll walk through provisioning the Jenkins instance, configuring build jobs, setting up SSH-based deployment, and testing the full end-to-end workflow. By the end, you\u2019ll have a fully functioning CI\/CD pipeline tailored to your infrastructure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why Deploy to UpCloud From Jenkins?<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jenkins and UpCloud make a great combination for developers who want full control over their deployment workflows without relying on third-party automation platforms. With Jenkins, you can customize every step of your CI\/CD pipeline, from pulling code from GitHub to running tests and deploying to production. It\u2019s especially useful for teams that want to self-host their automation and fine-tune their build and release processes without being locked into a proprietary system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">UpCloud, on the other hand, provides a fast, reliable, and cost-effective cloud hosting environment that pairs perfectly with Jenkins. You can spin up dedicated servers with just the right specs for your application, and scale vertically as needed without downtime. With features like floating IPs, private networking, and backups, UpCloud gives you the flexibility to run production workloads with confidence, whether you\u2019re hosting a small API service or a full-stack web app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using Jenkins on UpCloud means you\u2019re running your entire CI\/CD pipeline on infrastructure you control. It\u2019s an ideal setup for teams with specific compliance requirements, performance needs, or deployment strategies that are too complex for hosted solutions. Whether you\u2019re a solo developer or managing a growing team, deploying from Jenkins to UpCloud offers a powerful, flexible foundation for modern app development.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Overview and Prerequisites<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this tutorial, you will first fork <a href=\"https:\/\/github.com\/krharsh17\/random-facts-generator\" target=\"_blank\" rel=\"noopener\">this GitHub repository<\/a> that has a basic Node.js app. After that, you\u2019ll set up a new server on UpCloud where the app will be hosted. Finally, you will deploy the app manually on the server for the first time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next, you\u2019ll set up another UpCloud server to host an instance of Jenkins. You will then configure a pipeline on this Jenkins instance to handle future deployments of your Node.js app for you. Every time you make a change and push it to GitHub, your Jenkins instance will automatically update the app on your UpCloud server. This helps you skip the manual steps and keeps your app always up to date.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To follow along, you will need the following:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A GitHub account. You will use this account to fork your copy of <a href=\"https:\/\/github.com\/krharsh17\/random-facts-generator\" target=\"_blank\" rel=\"noopener\">this GitHub repository<\/a> on which you will set up the CI\/CD workflow. You will learn more about this repository shortly.<\/li>\n\n\n\n<li>An UpCloud account. You can <a href=\"https:\/\/signup.upcloud.com\/\">sign up for a free trial here<\/a>.<\/li>\n\n\n\n<li>Basic understanding of SSH. Here are a few resources to help you out:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/use-ssh-keys-authentication\/\">How to use SSH keys for authentication<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/managing-ssh-keys\/\">How to manage SSH keys for Cloud Servers<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have these in place, let\u2019s now take a quick look at the example app that you will deploy through the pipeline in this tutorial. The app in the repository is a random facts generator. It has a list of a few facts in the source code, and it picks and returns a fact from it randomly on each API call:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\/\/ contents of index.js\nconst express = require('express');\nconst app = express();\nconst PORT = process.env.PORT || 5001;\n\n\/\/ Array of random facts\nconst facts = [\n    \"Honey never spoils.\",\n    \"A group of flamingos is called a 'flamboyance.'\",\n    \"Bananas are berries, but strawberries aren't.\",\n    \"Octopuses have three hearts.\",\n    \"Australia is believed to have lost to emus in the Great Emu War of 1932\",\n    \"A jiffy is an actual unit of time: 1\/100th of a second.\",\n    \"Cows have best friends and get stressed when they are separated.\",\n    \"The shortest war in history lasted 38 minutes.\",\n    \"The Eiffel Tower can be 15 cm taller during the summer.\",\n    \"A small child could swim through the veins of a blue whale.\"\n];\n\n\/\/ Serve static files (HTML, CSS, JS)\napp.use(express.static('public'));\n\n\/\/ Endpoint to get a random fact\napp.get('\/random-fact', (req, res) =&gt; {\n    const randomIndex = Math.floor(Math.random() * facts.length);\n    res.json({ fact: facts[randomIndex] });\n});\n\n\/\/ Start the server\napp.listen(PORT, () =&gt; {\n    console.log(`Server is running on http:\/\/localhost:${PORT}`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It also serves a simple HTML page on its root path (<code>\/<\/code>). This page contains a title, a button to get a random fact, a placeholder for the random fact to be displayed to the user, and a footer. Here\u2019s what the code for this page looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\n    &lt;title&gt;Random Facts Generator&lt;\/title&gt;\n    &lt;style&gt;\n        body {\n            font-family: Arial, sans-serif;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            height: 98vh;\n            background-color: #f5f5f5;\n        }\n        button {\n            padding: 10px 20px;\n            font-size: 16px;\n            cursor: pointer;\n        }\n        p {\n            margin-top: 20px;\n            font-size: 20px;\n            text-align: center;\n        }\n\n        #footer {\n            position: absolute;\n            bottom: 10px;\n            font-size: 0.8rem;\n        }\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n\n&lt;h1&gt;Random Fact Generator&lt;\/h1&gt;\n&lt;button id=\"generate\"&gt;Get a Random Fact&lt;\/button&gt;\n&lt;p id=\"fact\"&gt;&lt;\/p&gt;\n&lt;p id=\"footer\"&gt;Made in 2024&lt;\/p&gt;\n\n&lt;script&gt;\n    document.getElementById('generate').onclick = async () =&gt; {\n        const response = await fetch('\/random-fact');\n        const data = await response.json();\n        document.getElementById('fact').innerText = data.fact;\n    };\n&lt;\/script&gt;\n\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can try running the app locally using the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># Clone the repo\ngit clone https:\/\/github.com\/krharsh17\/random-facts-generator\ncd random-facts-generator\n\n# Install dependencies\nyarn\n\n# Run the development server\nyarn dev<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, you can go to <code>http:\/\/localhost:5001<\/code> to view the app in action:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-206-1024x588.png\" alt=\"-\" class=\"wp-image-56469\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once you click the <strong>Get a Random Fact<\/strong> button, you will, indeed, get a random fact.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-207-1024x588.png\" alt=\"-\" class=\"wp-image-56471\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">At this point, please make sure that you fork a copy of this repo to your GitHub account. This is important because you will need to make changes to the source code of the app and then push the changes to your remote copy of the repository to trigger the Jenkins pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, let\u2019s move on to setting up the UpCloud server where you will deploy this application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Setting up an UpCloud Server through <code>upctl<\/code><\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">UpCloud provides two methods to create web servers: the web dashboard and the <a href=\"https:\/\/github.com\/UpCloudLtd\/upcloud-cli\" target=\"_blank\" rel=\"noopener\">upctl CLI<\/a>. In this section, you will create an UpCloud server through <code>upctl.<\/code> You can find detailed instructions on how to set up upctl on your system <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/get-started-upcloud-command-line-interface\/\">here<\/a> based on your local machine\u2019s operating system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have set up <code>upctl <\/code>correctly and logged into it with your UpCloud account, you can try it out by running the following command to print the details of your UpCloud account:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 upctl account show\n  \n  Username: &lt;your-upcloud-username&gt; \n  Credits:  &lt;your-credits&gt;     \n  \n  Resource Limits:\n    Cores:                      100 \n    Detached Floating IPs:       10 \n    Load balancers:              50 \n    Managed object storages:     20 \n    Memory:                  307200 \n    Network peerings:           100 \n    Networks:                   100 \n    NTP excess GiB:               0 \n    Public IPv4:                 20 \n    Public IPv6:                100 \n    Storage HDD:              10240 \n    Storage MaxIOPS:          10240 \n    Storage SSD:              10240<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now you\u2019re ready to create a new server! But before doing that, it is important to understand the various options you will need to supply to the <code>upctl create server <\/code>command.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Server Location<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll need to choose the physical location of your server. Ideally, you should choose the location closest to your target users so that they face the least latency when trying to access your app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To view a list of available zones, run <code>upctl zone list<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 upctl zone list\n\n ID        Description    Public \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n au-syd1   Sydney #1      yes    \n de-fra1   Frankfurt #1   yes    \n es-mad1   Madrid #1      yes    \n fi-hel1   Helsinki #1    yes    \n fi-hel2   Helsinki #2    yes    \n nl-ams1   Amsterdam #1   yes    \n pl-waw1   Warsaw #1      yes    \n se-sto1   Stockholm #1   yes    \n sg-sin1   Singapore #1   yes    \n uk-lon1   London #1      yes    \n us-chi1   Chicago #1     yes    \n us-nyc1   New York #1    yes    \n us-sjo1   San Jose #1    yes<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Server Plan<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Next, you need to choose a <em>server plan<\/em>. The server plan defines the CPU, RAM, and storage space available to your server. UpCloud provides you with a range of options from development-focused, small-sized plans for testing purposes and personal projects to large and cost-effective cloud-native plans that unbundle storage and IPv4 addresses from the plans. You can learn more about the available plans <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/cloud-server-plans\/\">here<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can list the available server plans by running <code>upctl server plans:<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 upctl server plans\n\n  General purpose\n\n     Name                       Cores   Memory   Storage size   Storage tier   Transfer out (GiB\/month) \n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     1xCPU-1GB                      1     1024             25   maxiops                            1024 \n     1xCPU-2GB                      1     2048             50   maxiops                            2048 \n     CLOUDNATIVE-1xCPU-4GB          1     4096              0                                      1024 \n     CLOUDNATIVE-1xCPU-8GB          1     8192              0                                      2048 \n     2xCPU-2GB                      2     2048             60   maxiops                            3072 \n     CLOUDNATIVE-2xCPU-4GB          2     4096              0                                      2048 \n     2xCPU-4GB                      2     4096             80   maxiops                            4096 \n     CLOUDNATIVE-2xCPU-8GB          2     8192              0                                      2048 \n     CLOUDNATIVE-2xCPU-16GB         2    16384              0                                      3072 \n     CLOUDNATIVE-4xCPU-8GB          4     8192              0                                      2048 \n     4xCPU-8GB                      4     8192            160   maxiops                            5120 \n     CLOUDNATIVE-4xCPU-16GB         4    16384              0                                      5120 \n     CLOUDNATIVE-4xCPU-24GB         4    24576              0                                      6144 \n     CLOUDNATIVE-4xCPU-32GB         4    32768              0                                      7168 \n     CLOUDNATIVE-4xCPU-48GB         4    49152              0                                     14336 \n     CLOUDNATIVE-6xCPU-16GB         6    16384              0                                      5120 \n     6xCPU-16GB                     6    16384            320   maxiops                            6144 \n     CLOUDNATIVE-6xCPU-24GB         6    24576              0                                      6144 \n     CLOUDNATIVE-8xCPU-16GB         8    16384              0                                      6144 \n     CLOUDNATIVE-8xCPU-24GB         8    24576              0                                      7168 \n     CLOUDNATIVE-8xCPU-32GB         8    32768              0                                      9216 \n     8xCPU-32GB                     8    32768            640   maxiops                            7168 \n     CLOUDNATIVE-8xCPU-48GB         8    49152              0                                     14336 \n     CLOUDNATIVE-8xCPU-64GB         8    65536              0                                     16384 \n     CLOUDNATIVE-8xCPU-96GB         8    98304              0                                     26624 \n     CLOUDNATIVE-8xCPU-128GB        8   131072              0                                     30720 \n     CLOUDNATIVE-12xCPU-24GB       12    24576              0                                      8192 \n     CLOUDNATIVE-12xCPU-32GB       12    32768              0                                      9216 \n     12xCPU-48GB                   12    49152            960   maxiops                            9216 \n     CLOUDNATIVE-16xCPU-32GB       16    32768              0                                     13312 \n     CLOUDNATIVE-16xCPU-48GB       16    49152              0                                     15360 \n     CLOUDNATIVE-16xCPU-64GB       16    65536              0                                     20480 \n     16xCPU-64GB                   16    65536           1280   maxiops                           10240 \n     CLOUDNATIVE-16xCPU-96GB       16    98304              0                                     26624 \n     CLOUDNATIVE-16xCPU-128GB      16   131072              0                                     35840 \n     CLOUDNATIVE-16xCPU-192GB      16   196608              0                                     46080 \n     CLOUDNATIVE-20xCPU-64GB       20    65536              0                                     22528 \n     CLOUDNATIVE-20xCPU-96GB       20    98304              0                                     28672 \n     24xCPU-96GB                   24    98304           1920   maxiops                           12288 \n     CLOUDNATIVE-24xCPU-256GB      24   262144              0                                     61440 \n     CLOUDNATIVE-32xCPU-64GB       32    65536              0                                     25600 \n     CLOUDNATIVE-32xCPU-128GB      32   131072              0                                     40960 \n     32xCPU-128GB                  32   131072           2048   maxiops                           24576 \n     CLOUDNATIVE-32xCPU-192GB      32   196608              0                                     51200 \n     CLOUDNATIVE-32xCPU-256GB      32   262144              0                                     66560 \n     CLOUDNATIVE-32xCPU-384GB      32   393216              0                                     76800 \n     38xCPU-192GB                  38   196608           2048   maxiops                           24576 \n     48xCPU-256GB                  48   262144           2048   maxiops                           24576 \n     CLOUDNATIVE-48xCPU-384GB      48   393216              0                                     81920 \n     CLOUDNATIVE-48xCPU-512GB      48   524288              0                                     92160 \n     CLOUDNATIVE-64xCPU-192GB      64   196608              0                                     56320 \n     CLOUDNATIVE-64xCPU-256GB      64   262144              0                                     71680 \n     CLOUDNATIVE-64xCPU-384GB      64   393216              0                                     87040 \n     64xCPU-384GB                  64   393216           2048   maxiops                           24576 \n     CLOUDNATIVE-64xCPU-512GB      64   524288              0                                     97280 \n     CLOUDNATIVE-80xCPU-512GB      80   524288              0                                    102400 \n     80xCPU-512GB                  80   524288           2048   maxiops                           24576 \n    \n  High CPU\n\n     Name                 Cores   Memory   Storage size   Storage tier   Transfer out (GiB\/month) \n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     HICPU-8xCPU-12GB         8    12288            100   maxiops                            4096 \n     HICPU-8xCPU-16GB         8    16384            200   maxiops                            4096 \n     HICPU-16xCPU-24GB       16    24576            100   maxiops                            5120 \n     HICPU-16xCPU-32GB       16    32768            200   maxiops                            5120 \n     HICPU-32xCPU-48GB       32    49152            200   maxiops                            6144 \n     HICPU-32xCPU-64GB       32    65536            300   maxiops                            6144 \n     HICPU-64xCPU-96GB       64    98304            200   maxiops                            7168 \n     HICPU-64xCPU-128GB      64   131072            300   maxiops                            7168 \n    \n  High memory\n\n     Name                 Cores   Memory   Storage size   Storage tier   Transfer out (GiB\/month) \n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     HIMEM-2xCPU-8GB          2     8192            100   maxiops                            2048 \n     HIMEM-2xCPU-16GB         2    16384            100   maxiops                            2048 \n     HIMEM-4xCPU-32GB         4    32768            100   maxiops                            4096 \n     HIMEM-4xCPU-64GB         4    65536            200   maxiops                            4096 \n     HIMEM-6xCPU-128GB        6   131072            300   maxiops                            6144 \n     HIMEM-8xCPU-192GB        8   196608            400   maxiops                            8192 \n     HIMEM-12xCPU-256GB      12   262144            500   maxiops                           10240 \n     HIMEM-16xCPU-384GB      16   393216            600   maxiops                           12288 \n     HIMEM-24xCPU-512GB      24   524288            700   maxiops                           12288 \n    \n  Developer\n\n     Name                 Cores   Memory   Storage size   Storage tier   Transfer out (GiB\/month) \n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     DEV-1xCPU-1GB-10GB       1     1024             10   standard                           1024 \n     DEV-1xCPU-1GB            1     1024             20   standard                           1024 \n     DEV-1xCPU-2GB            1     2048             30   standard                           1536 \n     DEV-1xCPU-4GB            1     4096             40   standard                           2048 \n     DEV-2xCPU-4GB            2     4096             60   standard                           2560 \n     DEV-2xCPU-8GB            2     8192             80   standard                           3072 \n     DEV-2xCPU-16GB           2    16384            100   standard                           4096 <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For this tutorial, choose the first option in the <em>Developer<\/em> list (1 CPU core, 1 GB RAM, and 10 GB storage), named <code>DEV-1xCPU-1GB-10GB.<\/code><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Operating System<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After you\u2019ve decided on the server plan, it is time to choose the operating system for the server. UpCloud provides you with a few popular public templates to choose from, along with the option of a wider variety of distributions <a href=\"https:\/\/upcloud.com\/global\/resources\/tutorials\/booting-server-live-cd\">through CDROMs<\/a> and the ability to download and install nearly any other possible operating system using <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/importing-server-image\/\">custom images<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can find a list of available templates <a href=\"https:\/\/upcloud.com\/global\/docs\/products\/block-storage\/templates\/\">here<\/a>. There are lots of technical considerations, opinions, and personal\/project preferences that need to be taken into account before making this choice. However, for this tutorial, you can select <strong>AlmaLinux 9<\/strong> as it is a lightweight option compared to enterprise distributions of Linux.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Note: The commands used in this tutorial have been written with AlmaLinux in mind. If you choose to use a different OS, you might run into unexpected issues.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>SSH keys<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You will need to set up an SSH key pair to be able to remotely log into the server. You need to create an SSH key pair and provide upctl with its public key. You can follow <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/use-ssh-keys-authentication\/\">this quick guide<\/a> to generate the SSH key pair based on your local operating system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will need to provide the location of the public key file when running the <code>upctl server create <\/code>command.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Initialization Script<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, you have the option to add an <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/initialization-scripts\/\">initialization script<\/a> to the deployment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Normally, right after you set up a server, you will want to do certain things on it before you start deploying your applications on it. These tasks can include anything from upgrading installed packages to configuring tools and setting up user accounts. Initialization scripts help you automate these tasks instead of having to manually do them after the server is deployed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since you will be deploying a Node.js application to this server, you need to ensure that Node is installed on it. You will also need the <code>git<\/code> CLI tool to interact with the repository and pull in the code. And, since the project uses <code>yarn<\/code> to manage dependencies and scripts, you will need to install it as well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To have the server automatically do all of that after it is deployed, save the following script in a file named <code>init-script.sh<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">#!\/bin\/bash \nsudo dnf update -y\nsudo dnf install curl dnf-plugins-core -y\nsudo dnf module install nodejs:22 -y\nsudo dnf install git -y\nnpm install --global yarn<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remember to add the line <code>#!\/bin\/bash<\/code> to the top of the script to make sure the OS understands what kind of interpreter it needs to run this script. You will pass the location of this <code>init-script.sh<\/code> file in the <code>server create<\/code> command to have UpCloud run this script as soon as the server is deployed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating the app server<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now that you understand all of the configuration options for the server and have prepared the SSH key and the initialization script, here is the command you need to run to create the server where you\u2019ll run your Node.js app.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">upctl server create \\\n  --title \"Random Facts Generator Server\" \\\n  --zone sg-sin1 \\\n  --os \"AlmaLinux 9\" \\\n  --hostname random-facts-generator-server \\\n  --ssh-keys id_rsa_gh.pub \\\n  --plan DEV-1xCPU-1GB-10GB \\\n  --user-data \"$(cat init-script.sh)\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a quick explanation of the arguments and values used:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>title<\/code>: sets the name of the server<\/li>\n\n\n\n<li><code>zone<\/code>: allows you to choose the location of the server.<\/li>\n\n\n\n<li><code>os<\/code>: allows you to choose the operating system for your server. Not including this would set up the server with Ubuntu Server 24.04<\/li>\n\n\n\n<li><code>hostname<\/code>: allows you to set up the hostname<\/li>\n\n\n\n<li><code>ssh-keys<\/code>: allows you to provide the public SSH key for the server as a file<\/li>\n\n\n\n<li><code>plan<\/code>: allows you to choose the server plan.<\/li>\n\n\n\n<li><code>user-data<\/code>: allows you to provide an initialization script to the server. This takes in a string value, so the command above has been configured to access the script stored in the file <code>init-script<\/code> through the <code>cat <\/code>command.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Once you run the command, you will receive a similar output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2713 Creating server random-facts-generator-server                                                                                                                                             10 s\n  \n  UUID         00afeedb-f55a-4c2d-80ae-9298ac9120b9     \n  IP Addresses 2a04:3543:1000:2310:78a8:8fff:feba:689f, \n               10.10.13.76,                             \n               213.163.197.191<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can run <code>upctl server list<\/code> to view the list of active servers along with their state:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 upctl server list\n\n UUID                                   Hostname                        Plan                 Zone      State       \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 00afeedb-f55a-4c2d-80ae-9298ac9120b9   random-facts-generator-server   DEV-1xCPU-1GB-10GB   sg-sin1   started \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once the state is <strong>started<\/strong>, you can SSH into the server using the command <code>ssh root@&lt;server-ip-address&gt;.<\/code> You can use upctl to retrieve the server IP address by running the command <code>upctl server show &lt;server-name&gt;<\/code>:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 upctl server show random-facts-generator-server\n  \n  Common\n    UUID:          00afeedb-f55a-4c2d-80ae-9298ac9120b9 \n    Hostname:      random-facts-generator-server        \n    Title:         Random Facts Generator Server        \n    Plan:          DEV-1xCPU-1GB-10GB                   \n    Zone:          sg-sin1                              \n    State:         started                              \n    Simple Backup: no                                   \n    Licence:       0                                    \n    Metadata:      True                                 \n    Timezone:      UTC                                  \n    Host ID:       4565254787                           \n    Server Group:                                       \n    Tags:                                               \n\n  Labels:\n\n    No labels defined for this resource.\n    \n  Storage: (Flags: B = bootdisk, P = part of plan)\n\n     UUID                                   Title                              Type   Address    Size (GiB)   Encrypted   Flags \n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     01a2d500-3374-438e-936e-ad43ea3aae32   random-facts-generator-server-OS   disk   virtio:0           10   no          P     \n    \n  NICs: (Flags: S = source IP filtering, B = bootable)\n\n     #   Type      IP Address                                      MAC Address         Network                                Flags \n    \u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n     1   public    IPv4: 213.163.197.191                           7a:a8:8f:ba:72:9d   0376454f-48b5-4d43-8008-1755c3d792f0   S     \n     2   utility   IPv4: 10.10.13.76                               7a:a8:8f:ba:99:23   03d31b4f-6fed-4ebc-947b-8973c008e3a4   S     \n     3   public    IPv6: 2a04:3543:1000:2310:78a8:8fff:feba:689f   7a:a8:8f:ba:68:9f   03000000-0000-4000-8030-000000000000   S     <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019re now ready to deploy the application for the first time!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Deploying the Application for the First Time<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before you start deploying the application, make sure you have forked it to your GitHub account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once ready, SSH into the server and run the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># Clone your forked repository\ngit clone https:\/\/github.com\/&lt;your-github-username&gt;\/random-facts-generator.git\n\n# Change into the cloned directory\ncd random-facts-generator\n\n# Install dependencies\nyarn\n\n# Start the production server\nyarn start:prod<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will start the Node.js application in a background process on the server. The Express app is configured to run on port 5001, so you should now be able to see the deployed app in action on the web address <code>http:\/\/&lt;your-server-ip&gt;:5001<\/code>:<br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-209-1024x548.png\" alt=\"-\" class=\"wp-image-56505\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This indicates that your server and app have been deployed successfully! Now, it\u2019s time to set up an automated pipeline that redeploys your app whenever you push a new commit to the <code>main<\/code> branch.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Self-hosting Jenkins on UpCloud<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To create the pipeline, you will first need to set up Jenkins on a server. To do that, you\u2019ll first create a new UpCloud server using the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">upctl server create \\\n  --title \"Jenkins Host\" \\\n  --zone sg-sin1 \\\n  --os \"AlmaLinux 9\" \\\n  --hostname jenkins-host \\\n  --ssh-keys id_rsa_gh.pub \\\n  --plan 1xCPU-2GB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This uses all the same options as the previous server create command, except for the server plan (this one uses the plan with 2GB RAM instead of 1GB) and the initialization script.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the server is deployed, SSH into it and run the following commands to install and start the Jenkins server:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo wget -O \/etc\/yum.repos.d\/jenkins.repo \\\n    https:\/\/pkg.jenkins.io\/redhat-stable\/jenkins.repo\nsudo rpm --import https:\/\/pkg.jenkins.io\/redhat-stable\/jenkins.io-2023.key\nsudo dnf upgrade\n\n# Add required dependencies for the jenkins package\nsudo dnf install fontconfig java-21-openjdk\nsudo dnf install jenkins\n\nsudo systemctl daemon-reload<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, you need to configure the Jenkins server to start at machine boot, and manually start it once:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo systemctl enable jenkins\nsudo systemctl start jenkins<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, you can go to the URL `http:\/\/&lt;your-server-ip&gt;:8080&#8243; and set up your Jenkins server through the guided process. Make sure to install the community-recommended plugins (or at least the git plugin) and configure an admin user in the process.Furthermore, you will also need to install the <strong>NodeJS<\/strong> plugin to be able to run Node.js builds. To do that, head over to <strong>Dashboard<\/strong> &gt; <strong>Manage Jenkins<\/strong> &gt; <strong>Plugins<\/strong> and search and install the NodeJS plugin:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-210-1024x524.png\" alt=\"-\" class=\"wp-image-56511\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have installed Node.js on the server, go to <strong>Dashboard<\/strong> &gt; <strong>Manage Jenkins<\/strong> &gt; <strong>Tools<\/strong>. Scroll to the bottom of the page and click the <strong>Add NodeJS<\/strong> button to set up the NodeJS installation:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-211-1024x524.png\" alt=\"-\" class=\"wp-image-56513\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the new section that appears, name your installation \u201c24\u201d and make sure to set the latest version of Node.js in the <strong>Version<\/strong> dropdown. Once done, click on the <strong>Save<\/strong> button at the bottom of the page:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-212-1024x524.png\" alt=\"-\" class=\"wp-image-56515\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once you\u2019ve finished setting up the Jenkins server, you can start creating the pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating the Jenkins pipeline<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Log in to your Jenkins admin dashboard, and either click on the <strong>+ New Item<\/strong> option from the left navigation pane or the <strong>Create new job<\/strong> button in the middle of the page:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-213-1024x524.png\" alt=\"-\" class=\"wp-image-56516\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the <strong>New Item<\/strong> page, select <strong>Pipeline<\/strong> as item type and enter a name for the pipeline. Once done, click on the <strong>OK<\/strong> button.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-214-1024x524.png\" alt=\"-\" class=\"wp-image-56517\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You will now need to configure the pipeline. First of all, check the <strong>Github project<\/strong> option in the <strong>General<\/strong> section and paste the URL of your forked GitHub repository:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-215-1024x524.png\" alt=\"-\" class=\"wp-image-56520\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Next, scroll below to the <strong>Triggers<\/strong> section and check the <strong>GitHub hook trigger for GITScm polling<\/strong> option:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-216-1024x522.png\" alt=\"-\" class=\"wp-image-56523\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This means that the pipeline will be triggered whenever Jenkins receives a GitHub <code>push<\/code> hook for this repository. To make this work, you will need to configure the GitHub plugin to receive hooks from your GitHub account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To do that, follow either the manual setup or the automated setup instructions in <a href=\"https:\/\/plugins.jenkins.io\/github\/\" target=\"_blank\" rel=\"noopener\">this guide by Jenkins<\/a>. Make sure to configure the GitHub plugin correctly before moving ahead.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><em>A quick method for now would be to go to <\/em><strong><em>Dashboard<\/em><\/strong><em> &gt; <\/em><strong><em>Manage Jenkins<\/em><\/strong><em> &gt; <\/em><strong><em>System<\/em><\/strong><em> and copy the hook URL from the <\/em><strong><em>?<\/em><\/strong><em> tooltip next to the <\/em><strong><em>GitHub Servers<\/em><\/strong><em> section and add it to your Github repo\u2019s settings &gt; <\/em><strong><em>Webhooks<\/em><\/strong><em>.<\/em><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Once done, scroll down to the <strong>Pipeline<\/strong> section in the pipeline configuration page and paste the following script in the <strong>Script<\/strong> textbox:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">pipeline {\n    agent any\n    \n    tools {\n        nodejs '24'\n    }\n    \n    environment {\n        SERVER_IP = '&lt;your-server-ip-here&gt;'\n        UPCLOUD_USERNAME = 'root'\n    }\n    \n    stages {\n        stage('Checkout') {\n            steps {\n                checkout scmGit(\n                    branches: [[name: 'main']],\n                    userRemoteConfigs: [[url: 'https:\/\/github.com\/krharsh17\/random-facts-generator.git']]\n                )\n            }\n        }\n\n        \n        stage('Install Dependencies') {\n            steps {\n                sh 'npx yarn'\n            }\n        }\n        \n        stage('Deploy') {\n            steps {\n                withCredentials([sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY')]) {\n                    sh '''\n                        # Start SSH agent\n                        eval $(ssh-agent -s)\n                        \n                        # Set proper permissions and add key\n                        chmod 400 $DEPLOY_KEY\n                        ssh-add $DEPLOY_KEY\n                        \n                        # Deploy to server\n                        ssh -o StrictHostKeyChecking=no $UPCLOUD_USERNAME@$SERVER_IP \/bin\/bash &lt;&lt; 'EOT'\n                          cd \/random-facts-generator\n                          git pull origin main\n                          yarn restart:prod\nEOT\n                    '''\n                }\n            }\n        }\n    }\n    \n    post {\n        always {\n            cleanWs()\n        }\n        success {\n            echo 'Pipeline completed successfully!'\n        }\n        failure {\n            echo 'Pipeline failed!'\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once done, click on the <strong>Save<\/strong> button at the bottom of the page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a quick explanation of the Jenkinsfile:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>agent<\/strong>: Specifies where the pipeline should run. agent any tells Jenkins to run this pipeline on any available agent.<\/li>\n\n\n\n<li><strong>tools<\/strong>: Declares the tools required for the pipeline. In this case, it specifies the use of the Node.js installation named \u201824\u2019, which you configured earlier.<\/li>\n\n\n\n<li><strong>environment<\/strong>: Defines environment variables that can be accessed throughout the pipeline. Make sure to fill your server ID in <code>SERVER_IP<\/code> and \u201croot\u201d in <code>UPCLOUD_USERNAME<\/code>.<\/li>\n\n\n\n<li><strong>stages<\/strong>: Defines the steps of the pipeline, broken into logical sections\n<ul class=\"wp-block-list\">\n<li><strong>Checkout stage<\/strong>: Clones the main branch of the GitHub repository using the specified remote URL. It uses <code>checkout scmGit(...)<\/code> instead of <code>checkout scm<\/code> to explicitly configure the git repository source.<\/li>\n\n\n\n<li><strong>Install dependencies stage<\/strong>: Installs project dependencies using Yarn via <code>npx yarn<\/code>, which ensures the local Yarn version is used even if Yarn isn\u2019t globally installed.<\/li>\n\n\n\n<li><strong>Deploy stage<\/strong>: Uses SSH credentials securely retrieved from Jenkins (<code>deploy-key<\/code>) to connect to the server. It:\n<ul class=\"wp-block-list\">\n<li>Starts the SSH agent<\/li>\n\n\n\n<li>Sets the right permissions on the private key<\/li>\n\n\n\n<li>Connects to the remote server via SSH<\/li>\n\n\n\n<li>Navigates to the project directory<\/li>\n\n\n\n<li>Pulls the latest code from the main branch<\/li>\n\n\n\n<li>Restarts the app using a custom Yarn script <code>restart:prod<\/code> (you\u2019ll need this script defined in package.json)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>post<\/strong>: Defines actions to run after the pipeline completes:\n<ul class=\"wp-block-list\">\n<li><code>always<\/code>: Cleans up the workspace after the job finishes, regardless of success or failure.<\/li>\n\n\n\n<li><code>success<\/code>: Prints a success message to the Jenkins console log.<\/li>\n\n\n\n<li><code>failure<\/code>: Prints a failure message to help identify issues during pipeline execution.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Before you can run this pipeline, you need to configure the deploy key as a secure credential in the Jenkins server.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Setting up the Deploy Key<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Jenkins offers a more convenient and structured way to handle credentials, and especially SSH keys. To provide your SSH key to Jenkins, go to <strong>Dashboard<\/strong> &gt; <strong>Manage Jenkins<\/strong> &gt; <strong>Credentials<\/strong> and click on <strong>System<\/strong> under the <strong>Stores scoped to Jenkins<\/strong>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-217-1024x524.png\" alt=\"-\" class=\"wp-image-56527\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">On the next page that opens, click on the <strong>Global credentials (unrestricted)<\/strong> domain in the list. In production settings, you should choose to create a separate domain for your project to further implement abstraction:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-218-1024x524.png\" alt=\"-\" class=\"wp-image-56530\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Here, click on the <strong>+ Add Credentials<\/strong> button at the top right.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-219-1024x524.png\" alt=\"-\" class=\"wp-image-56533\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">On the <strong>New credentials<\/strong> page, set:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Kind<\/strong>: <em>SSH username with private key<\/em><\/li>\n\n\n\n<li><strong>ID<\/strong>: <em>deploy-key<\/em><\/li>\n\n\n\n<li><strong>Username<\/strong>: <em>root<\/em><\/li>\n\n\n\n<li><strong>Private key<\/strong>: <em>Enter directly<\/em> and then paste the complete private key in the text box.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Once done, click the <strong>Create<\/strong> button at the bottom:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-220-1024x524.png\" alt=\"-\" class=\"wp-image-56535\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This will add the private key to the global scope of your Jenkins instance. This means that you can now access it in the pipeline and connect to your UpCloud server from the pipeline environment.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Running and Testing the Pipeline<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The pipeline is now set up and listening for new commits pushed to your repo. To test it out, you can try updating something in the repo, such as the footer text in the <strong>public\/index.html<\/strong> file that says \u201cMade in 2024\u201d to change it to \u201cMade in 2025\u201d. You will see that a new build gets triggered automatically:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-221-1024x524.png\" alt=\"-\" class=\"wp-image-56537\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Within a minute, you will see the build logs end with a <code>SUCCESS<\/code> message:<br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-222-1024x524.png\" alt=\"-\" class=\"wp-image-56539\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And in a minute or two, the text on your public website will be updated:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-223-1024x548.png\" alt=\"-\" class=\"wp-image-56540\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This means that your pipeline has been set up and connected to your UpCloud server successfully! This marks the end of the tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Troubleshooting Some Common Issues<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Setting up SSH keys and cloud scripts can sometimes be confusing. Here are a few common problems you might see, along with easy ways to fix them:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Host key verification failed<\/code>: This error means the Jenkins agent doesn\u2019t recognize the target server\u2019s SSH host key. To bypass this (especially in automated scripts), include the <code>-o StrictHostKeyChecking=no option<\/code> in your <code>ssh<\/code> command inside the <code>sh<\/code> block of your Jenkinsfile. This disables the interactive prompt and prevents the deployment from failing.<\/li>\n\n\n\n<li><code>Error loading key \"&lt;key-file-name\": error in libcrypto<\/code>: This indicates a problem with your private key file. Make sure the file is complete (no missing lines), starts with <code>-----BEGIN OPENSSH PRIVATE KEY-----<\/code>, and has the correct permissions (usually <code>chmod 400<\/code>).<\/li>\n\n\n\n<li><code>Permission denied (publickey)<\/code>: This usually means the SSH private key wasn\u2019t correctly passed to the ssh command. In your Jenkinsfile, verify that the key is loaded using <code>ssh-add $DEPLOY_KEY<\/code> and you\u2019re using Jenkins credentials properly via withCredentials([sshUserPrivateKey(&#8230;)])<\/li>\n\n\n\n<li><code>kex_exchange_identification: read: Connection reset by peer<\/code> or <code>Connection reset by <ip address=\"\"> port 22:<\/ip><\/code><ip address=\"\"> These errors typically mean that the server is rejecting the SSH connection at a network level. Possible causes include:<\/ip>\n<ul class=\"wp-block-list\">\n<li>Firewall rules blocking the Jenkins agent<\/li>\n\n\n\n<li>SSHD service not yet initialized on the server (common with newly provisioned cloud instances)<\/li>\n\n\n\n<li>Too many failed login attempts leading to temporary blocking<br>Try waiting a few seconds and re-running the job, or ensure your server is ready and accessible on port 22.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When deploying to a freshly provisioned server using a cloud-init script, some commands may fail silently. This could be due to missing <code>-y <\/code>flags (which skip interactive prompts), unavailable package versions, or temporary DNS issues. To debug such failures, you\u2019ll need to check the logs from when the script ran. You can do this by logging into the server and running <code>sudo grep cloud-init \/var\/log\/messages.<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Setting up Jenkins to deploy your Node.js application to UpCloud is a powerful way to automate your development workflow. Jenkins handles everything from dependency installation to testing and deployment with every push, saving time and reducing manual errors. This kind of automation is especially helpful for solo developers and small teams who want to maintain high development velocity without compromising on code quality.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">UpCloud makes a great foundation for this setup, offering fast and reliable cloud servers that can easily run both your application servers and your CI infrastructure. Whether you\u2019re hosting your production app, running Jenkins agents, or doing both on the same account, UpCloud\u2019s flexible compute options make it easy to scale resources as needed. With features like <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/scale-cloud-servers-hot-resize\/\">Hot Resize<\/a>, you can add CPU or memory to your servers without downtime, ensuring smooth growth as your apps and CI pipelines become more demanding.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ready to automate your deployments and scale with confidence? <a href=\"https:\/\/signup.upcloud.com\/\">Sign up for UpCloud<\/a> to launch your first server in minutes. Then connect your Jenkins pipeline using the Jenkinsfile from this guide and stay focused on shipping code, while UpCloud keeps your infrastructure fast, stable, and ready to grow.<\/p>\n","protected":false},"author":82,"featured_media":56547,"comment_status":"open","ping_status":"closed","template":"","community-category":[223],"class_list":["post-1855","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1855","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/types\/tutorial"}],"author":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/users\/82"}],"replies":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/comments?post=1855"}],"version-history":[{"count":0,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1855\/revisions"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=1855"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=1855"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}