{"id":1879,"date":"2025-05-20T08:00:00","date_gmt":"2025-05-20T05:00:00","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/deploying-a-node-js-application-from-travis-ci-to-upcloud\/"},"modified":"2026-03-09T14:47:12","modified_gmt":"2026-03-09T14:47:12","slug":"deploying-a-node-js-application-from-travis-ci-to-upcloud","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/deploying-a-node-js-application-from-travis-ci-to-upcloud\/","title":{"rendered":"Deploying a Node.js Application from Travis CI to UpCloud"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.travis-ci.com\/\" target=\"_blank\" rel=\"noopener\">Travis CI<\/a> and <a href=\"https:\/\/upcloud.com\/global\/\">UpCloud<\/a> make a strong pair for deploying modern web applications. Travis CI provides a straightforward continuous integration and deployment (CI\/CD) platform that supports Node.js and a variety of other frameworks out of the box, automating your workflow from code commit to production delivery. On the other hand, UpCloud\u2019s high-performance cloud infrastructure gives you the flexibility and speed needed to host scalable applications.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this guide, you\u2019ll walk through the process of deploying a Node.js app from Travis CI to an UpCloud server. You\u2019ll set up a simple <code>.travis.yml<\/code> configuration that connects securely to your UpCloud environment and automates deployments to reduce manual effort and ship faster with confidence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why Deploy to UpCloud From Travis CI?<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Travis CI and UpCloud work well together to help developers build and deploy apps more easily. With Travis CI, you don\u2019t need to do things like upload files or restart your app every time you make a change. Instead, Travis CI runs tests and sends the new version of your app to the server automatically. This kind of setup helps make sure your app is always working and up to date without much extra work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">UpCloud is a fast cloud hosting platform that gives you full control over your server. You can choose the amount of memory and CPU your app needs, and upgrade whenever your app gets bigger. This is great for small teams or solo developers who want good performance but don\u2019t want to manage everything from scratch. UpCloud makes it easy to run and host all kinds of apps, and it gives you the tools to grow your project without having to move to a new platform later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Putting Travis CI and UpCloud together is a smart choice for developers who want to spend more time writing code and less time worrying about deployments. The setup is simple and doesn\u2019t require a lot of DevOps knowledge. Whether you\u2019re a freelancer building client projects, a SaaS company deploying microservices, or a startup looking to scale your CI\/CD workload, this combo helps you move fast and stay focused.<\/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 connect your repository to Travis CI so that it can handle future deployments for you. Every time you make a change and push it to GitHub, Travis CI 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>A Travis CI account. You can <a href=\"https:\/\/app.travis-ci.com\/signup\" target=\"_blank\" rel=\"noopener\">sign up here<\/a>.<\/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 static HTML page on its root path (<code>\/<\/code>). This page contains a heading, a button to get a random fact, a placeholder for the random fact to be displayed to the user, and a footer at the bottom of the page. Here\u2019s the source code for this page:<\/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\">You can now go to <code>http:\/\/localhost:5001<\/code> to view the app in action:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/1-travis-ci-random-generator.png\" alt=\"-\" class=\"wp-image-58499\" \/><\/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-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/2-travis-ci-random-fact.png\" alt=\"-\" class=\"wp-image-58500\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Ensure that you fork a copy of this repo to your GitHub account before moving ahead. This is important because you will need to make changes to the source code of this app to trigger the CI\/CD pipeline and update the deployed application.<\/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\">You can use either the UpCloud web dashboard or the <a href=\"https:\/\/github.com\/UpCloudLtd\/upcloud-cli\" target=\"_blank\" rel=\"noopener\">upctl CLI<\/a> to set up an UpCloud web server. This section will walk you through creating an UpCloud server through upctl. You can find detailed instructions on how to set it up <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/get-started-upcloud-command-line-interface\/\">here<\/a> based on your operating system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have set up upctl correctly and authenticated with your UpCloud account, running the following command should 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 \n  Credits:  &lt;your-credits&gt;\n     \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. You should choose the location closest to you for the least latency when connecting to the server remotely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can run <code>upctl zone list<\/code> to view a list of available zones:<\/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 up, you\u2019ll 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 developer-focused small sizes 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 \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Since this is a test project, 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 hardware configuration, it\u2019s 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, so using a different OS might cause you to 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 server create 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 cloud server, you will have a list of things to do before you start deploying your applications on it. It 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 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.<br><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating the 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 collected the SSH keys and initialization script, here\u2019s the command you need to run to create the server.<\/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_fact-gen_ci.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                                                                                                               9 s\n  \n  UUID         004e656c-e8ad-4902-9350-a178e08482c0     \n  IP Addresses 2a04:3543:1000:2310:78a8:8fff:feba:7cf3, \n               10.10.2.65,                              \n               213.163.206.7     <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can run <code>upctl server<\/code> list 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\n 004e656c-e8ad-4902-9350-a178e08482c0   random-facts-generator-server   DEV-1xCPU-1GB-10GB   sg-sin1   started <\/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><\/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:          004e656c-e8ad-4902-9350-a178e08482c0 \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:       4296819331                           \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     01ca6a5e-0221-404d-a4fe-477d7457cec5   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.206.7                             7a:a8:8f:ba:62:98   03800413-da41-4b26-84a3-7d021425603b   S     \n     2   utility   IPv4: 10.10.2.65                                7a:a8:8f:ba:f1:0a   0372fde3-e376-4e4c-a646-22789035cec0   S     \n     3   public    IPv6: 2a04:3543:1000:2310:78a8:8fff:feba:7cf3   7a:a8:8f:ba:7c:f3   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>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/3-travis-ci-random-fact-generator.png\" alt=\"-\" class=\"wp-image-58502\" \/><\/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>Creating a Travis CI Pipeline<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To create the pipeline, you will first need to install the <a href=\"https:\/\/github.com\/apps\/travis-ci\" target=\"_blank\" rel=\"noopener\">Travis CI app on your GitHub account<\/a> and give it access to the forked repository. Here\u2019s what the app configuration page should look like when done:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/4-travis-ci-repository-access.png\" alt=\"-\" class=\"wp-image-58504\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Now, all you need to do is add a <code>.travis.yml <\/code>file to your repo, and Travis CI will pick up the new commit and start building!<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating the Travis CI config file<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To make things easier, you should install the <a href=\"https:\/\/github.com\/travis-ci\/travis.rb\" target=\"_blank\" rel=\"noopener\">Travis CI CLI<\/a>, which helps quickly initialise a Travis CI config file, easily set environment variables, and more. You can do that by running the following command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>gem install travis<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once installed, clone the forked GitHub repo locally and in the root of the repo directory, run the following command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>travis init<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enter \u201cJavaScript\u201d when prompted for the language used in the repository. This will create a barebones .travis.yml file in the repo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is where you will configure the pipeline. Here\u2019s what the workflow will look like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Configure the pipeline runner with your UpCloud server\u2019s private SSH key<\/li>\n\n\n\n<li>Connect to your UpCloud server through SSH from within the pipeline<\/li>\n\n\n\n<li>Run a <code>git pull <\/code>on the local repo on the server to get the latest code<\/li>\n\n\n\n<li>Restart the running PM2 process of the application using the <code>yarn restart:prod <\/code>command<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Some people might prefer using tools like <a href=\"https:\/\/www.freecodecamp.org\/news\/scp-linux-command-example-how-to-ssh-file-transfer-from-remote-to-local\/\" target=\"_blank\" rel=\"noopener\">scp<\/a> to directly copy files into the server instead of relying on fetching from a git repository independently, but since this tutorial uses a publicly accessible git repo, either method works fine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, this pipeline will only contain the deploy stage. This is because Node.js apps do not need to be built most of the time (unless you are using a dialect like TypeScript or CoffeeScript to write the code), and this repo does not contain any tests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, replace the contents of the <code>.travis.yml<\/code> file with the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">language: node_js\nnode_js:\n- '24'\ndist: noble\n\nbranches:\n  only:\n  - main\nenv:\n  global:\n  - SERVER_IP=&lt;your-server-ip-here&gt;\n  - UPCLOUD_USERNAME=root\ndeploy:\n  provider: script\n  script: bash deploy_script.sh\n  on:\n    branch: main<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>language<\/code> node defines the language that your app uses. The <code>node_js <\/code>specifies Node.js version 16 to be used for the build environment. The <code>dist <\/code>node sets the <a href=\"https:\/\/docs.travis-ci.com\/user\/reference\/linux\/\" target=\"_blank\" rel=\"noopener\">runner\u2019s Linux distribution<\/a> to Ubuntu Noble 24.04. If you are using Node.js 18 or above, you will need to target Ubuntu 20 or above, as these Node versions have GLIB requirements that are not backwards compatible with older Linux distributions. You can read more about it <a href=\"https:\/\/travis-ci.community\/t\/node-lib-x86-64-linux-gnu-libm-so-6-version-glibc-2-27-not-found-required-by-node\/13655\/2\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>branches: only: ['main'] <\/code>specifies that Travis should only run builds for the main branch. In the <code>env<\/code> node, two environment variables are defined for the server IP and the username. These will be used in the <code>ssh &lt;user&gt;@&lt;ip&gt; <\/code>command in the deploy script. Make sure to provide the right server IP in the SERVER_IP field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>deploy<\/code> node defines a deploy step, where you\u2019ll supply a script that logs in to your UpCloud server using your SSH private key and redeploy the Node.js app. You will need to provide this script in a <code>deploy_script.sh<\/code> file in the same directory. Here\u2019s what the file will look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">eval $(ssh-agent -s)\nchmod 400 deploy_key\nssh-add deploy_key\nssh -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<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This script first checks if the <code>ssh-agent<\/code> is installed on the build environment (which is true for Travis CI Node.js build runners), then sets the right permissions for the deploy key, adds it to the SSH keys list of the runner, and runs the SSH command using it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On the UpCloud server, the script does a fresh git pull of the repository and runs the restart:prod script. This redeploys the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So, the only thing you need now to make this work is the deploy key. You will use Travis CLI to supply that conveniently to the pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Supplying the Deploy Key<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Travis CI allows <a href=\"https:\/\/docs.travis-ci.com\/user\/encrypting-files\/\" target=\"_blank\" rel=\"noopener\">encrypting<\/a> and supplying files to its pipelines. This comes in handy for tasks like supplying credentials or other sensitive data to the pipelines without risking exposure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/docs.travis-ci.com\/user\/encrypting-files\/#automated-encryption\" target=\"_blank\" rel=\"noopener\">Travis CLI<\/a> makes it easy to create and add these files directly to a pipeline without having to manually encrypt the file and add it to the Travis CI server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To do this, you will first need to store your SSH private key in a file named <code>deploy_key<\/code> in the local repo root. Make sure to add this file to .gitignore so that you don\u2019t push it to the remote repo. Travis CLI will also remind you of the same.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have the private key file in place, run the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">travis encrypt-file deploy_key --add<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter <code>y<\/code> when asked for overwriting the config file. Here\u2019s what the output will look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">\u2717 travis encrypt-file deploy_key --add\nencrypting deploy_key for krharsh17\/random-facts-generator\nstoring result as deploy_key.enc\nstoring secure env variables for decryption\n\n\nOverwrite the config file \/Users\/kumarharsh\/Work\/UpCloud\/prod\/github\/random-facts-generator\/.travis.yml with the content below?\n\nThis reformats the existing file.\n\n---\nlanguage: node_js\nnode_js:\n- '24'\ndist: noble\n\nbranches:\n  only:\n  - main\nenv:\n  global:\n  - SERVER_IP=94.237.16.140\n  - UPCLOUD_USERNAME=root\ninstall:\n- npm install\ndeploy:\n  provider: script\n  script: bash deploy_script.sh\n  true:\n    branch: main\nbefore_install:\n- openssl aes-256-cbc -K $encrypted_189e52c2c347_key -iv $encrypted_189e52c2c347_iv\n  -in deploy_key.enc -out deploy_key -d\n\n\n(y\/N)\ny\n\nMake sure to add deploy_key.enc to the git repository.\nMake sure not to add deploy_key to the git repository.\nCommit all changes to your .travis.yml.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You will notice a new <code>deploy_key.enc<\/code> file and a new node in your <code>.travis.yml<\/code> file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\"># rest of your .travis.yml file\ndeploy:\n  provider: script\n  script: bash deploy_script.sh\n  on:\n    branch: main\n    \n# the new node\nbefore_install:\n- openssl aes-256-cbc -K $encrypted_189e52c2c347_key -iv $encrypted_189e52c2c347_iv\n  -in deploy_key.enc -out deploy_key -d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This <code>before_install<\/code> script<code> <\/code>will automatically retrieve the encryption key from the Travis CI environment and decrypt the encrypted <code>deploy_key.enc<\/code> file. This will allow you to use the SSH private key in your build environments without having to worry about its security.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your pipeline is now ready. All you need to do now is commit and push the <code>.travis.yml<\/code>, <code>deploy_key.enc<\/code>, and the <code>deploy_script.sh<\/code> files to your repo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you push the commit, you will see a new build get triggered:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/5-travis-ci-build-process.png\" alt=\"-\" class=\"wp-image-58505\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Within a minute, you should see the pipeline complete successfully.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/6-travis-ci-build-complete.png\" alt=\"-\" class=\"wp-image-58506\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This means that it has been set up correctly!<\/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-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/7-travis-ci-build-pipeline.png\" alt=\"-\" class=\"wp-image-58507\" \/><\/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-full\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/8-travis-ci-random-fact-generator-live.png\" alt=\"-\" class=\"wp-image-58510\" \/><\/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. You can find the code used in the tutorial in <a href=\"https:\/\/github.com\/krharsh17\/random-facts-generator\/tree\/travis\" target=\"_blank\" rel=\"noopener\">the <code>travis <\/code>branch of the GitHub repository<\/a>.<\/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 means that your Travis runner does not have the host key of the UpCloud server added to its <code>known_hosts<\/code> file. To avoid this altogether, make sure you\u2019re including the <code>-o StrictHostKeyChecking=no<\/code> option in your ssh command in the <code>deploy_script.sh<\/code> file<\/li>\n\n\n\n<li><code>Error loading key \"&lt;key-file-name\": error in libcrypto<\/code>: This means there\u2019s a problem with your private key file. Make sure the file has the full key, with no missing lines, and that it\u2019s in the right format.<\/li>\n\n\n\n<li><code>Permission denied (publickey)<\/code>: If you see this error when Travis tries to SSH into the server, it usually means your private key wasn\u2019t supplied with the SSH command. Make sure the key was added successfully using the <code>ssh-add<\/code> command. Also, make sure the key pair matches and has the right permissions.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When you run a cloud init script on a new server, some commands might fail. This can happen if a -y flag is missing for automatic approvals, or if a package version is wrong or not available. To figure out what went wrong, 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 Travis CI to deploy your Node.js application to UpCloud is a simple yet powerful way to save time and reduce manual work. With every push to your repository, Travis CI can handle the build and deployment steps for you. This means no more logging into servers or copying files by hand. Such an automation is especially helpful for small teams or solo developers who want to move fast without skipping important checks like testing and version control.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">UpCloud adds even more value by giving you fast, reliable cloud servers that you can customize as your app grows. You don\u2019t have to start over when you need more memory or CPU; just resize your server with zero downtime. Whether you\u2019re launching your first project or managing multiple apps, this setup lets you stay focused on your code while your infrastructure keeps up behind the scenes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ready to get started? <a href=\"https:\/\/signup.upcloud.com\/\">Sign up for UpCloud<\/a> and launch your high-performance server in minutes. Then connect your code to <a href=\"https:\/\/www.travis-ci.com\/\" target=\"_blank\" rel=\"noopener\">Travis CI<\/a> using the .travis.yml file from this guide to automate your deployments. As your app grows, take advantage of UpCloud\u2019s flexible infrastructure. UpCloud\u2019s <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/scale-cloud-servers-hot-resize\/\">Hot Resize<\/a> makes it easy to scale without interrupting your users!<\/p>\n","protected":false},"author":82,"featured_media":53008,"comment_status":"open","ping_status":"closed","template":"","community-category":[223],"class_list":["post-1879","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1879","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=1879"}],"version-history":[{"count":1,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1879\/revisions"}],"predecessor-version":[{"id":4531,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1879\/revisions\/4531"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=1879"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=1879"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}