← Back to list

From code to cloud: How I deployed MY Portfolio with a custom domain, Github collaboration &CI/CD.

Introduction

Krtbastola · 2026-06-19 03:09 · 0 claps · 16.1 min read
#docker-compose #portfolio #cloudflared
Open on Medium ↗
Wiki topics: INV · Investing & Markets ☁️ · DevOps & Cloud 🔓 · Open Source 📊 · Economic Policy

From code to cloud: How I deployed MY Portfolio with a custom domain, Github collaboration &CI/CD.

Introduction

Nowadays, many developers, students, and organizations need to expose their local web applications to the internet for testing, demonstrations, remote access, or even production use. Traditionally, this process requires purchasing a public server or Virtual Private Server (VPS), configuring firewalls, assigning static IP addresses, and opening ports on routers. These steps are often complex, time-consuming, and may introduce security risks if not configured properly.

To address these challenges, Cloudflare Tunnel provides a secure and modern alternative. It allows users to expose local services to the internet through Cloudflare’s global network without opening any inbound ports on their router or firewall. This approach significantly improves security while simplifying deployment and remote access.

In this blog, we will demonstrate how to:

· Register a domain name

· Connect the domain to Cloudflare

· Create a Cloudflare Tunnel

· Configure DNS records

· Publish a local web application using a custom domain

· Access the application securely over HTTPS from anywhere

By the end of this tutorial, you will have a fully functional Cloudflare Tunnel that securely exposes your local application to the public internet without requiring port forwarding.

Step 1: Register a Domain Name

Before creating a Cloudflare Tunnel, you need a domain name that will be used to access your local application over the internet.

For this tutorial, you can register a free .np domain from: register.com.np

Registration Process

Visit the website.

Search for your desired domain name.

Fill in the required personal or organizational details.

Upload the necessary supporting documents if requested.

Submit the application.

Wait for approval from the domain registrar.

After approval, the status of your domain will change to Active

For example:

example.com.np

myproject.com.np

labserver.com.np

Once the domain becomes active, it is ready to be connected with Cloudflare for DNS management and tunnel configuration.

Why Do We Need a Domain?

A domain name provides a human-readable address for your application. Instead of accessing your application through an IP address, users can simply visit:

https://app.example.com.np

https://demo.example.com.np

Using a domain with Cloudflare Tunnel also provides:

Automatic HTTPS certificates

Secure encrypted connections

Protection against exposing your home IP address

Easy DNS management

No need for router port forwarding

This domain registration is the first step toward securely publishing your local application to the internet using Cloudflare Tunnel. In the next section, we will connect the registered domain to Cloudflare and configure its DNS settings.

Step 2: Create the Project Directory and Docker Configuration

After setting up your domain, create a separate directory for your portfolio website project. This directory will contain the application source code along with the Docker configuration files required to containerize the application.

For example, create the following project structure:

portfolio-site/

├── Dockerfile

├── docker-compose.yml

├── index.html

├── css/

├── js/

└── assets/

The Dockerfile defines the instructions for building a Docker image for the portfolio website. It specifies the base image, copies the application files into the container, and configures the web server to serve the website.

The docker-compose.yml file is used to define and manage the Docker services. It simplifies the process of building and running containers by allowing all configurations to be stored in a single file.

Once the project files, Dockerfile, and docker-compose.yml are created, build your portfolio website image using Docker. After the image is successfully built, start the containerized application using Docker Compose.

Running the portfolio site inside a Docker container ensures consistency across different environments, simplifies deployment, and makes it easier to expose the application securely through Cloudflare Tunnel in the later stages of this tutorial.

Sudo docker build -t portfolio-site .

Sudo docker run -d –name portfolio -p 8080:80 portfolio-site

Sudo docker images, sudo docker ps

Step 3: Access the Portfolio Website Locally

After successfully building the Docker image and starting the container using Docker Compose, the portfolio website will be accessible on your local machine.

Open a web browser and navigate to:

http://localhost:8080

If the container is running correctly, your portfolio website should be displayed in the browser. This confirms that the application has been successfully built and deployed locally using Docker.

Testing the website locally before exposing it to the internet is an important step because it helps verify that the application is functioning as expected. Any issues related to the application, Docker configuration, or web server can be identified and resolved at this stage.

Once you have confirmed that the portfolio website is working properly on localhost:8080, you can proceed to configure Cloudflare Tunnel and expose the application securely to the internet using your custom domain.

Step 4: Install Cloudflared and Create a Temporary Tunnel

After verifying that the portfolio website is working correctly on http://localhost:8080, the next step is to install Cloudflared, the command-line tool used to create and manage Cloudflare Tunnels.

First, download and install Cloudflared on your operating system from the official Cloudflare website. After installation, verify that it is installed successfully by running:

cloudflared — version

The command should display the installed version of Cloudflared.

Next, create a temporary tunnel to expose your local application to the internet by executing the following command:

cloudflared tunnel — url http://localhost:8080

This command creates a secure tunnel between Cloudflare’s network and your local web application running on port 8080. Cloudflare automatically generates a temporary public URL similar to: **https://random-string.trycloudflare.com**

You can open this URL in any web browser to access your portfolio website from anywhere on the internet without configuring port forwarding or exposing your local IP address.

This temporary tunnel is useful for testing and demonstrations. In the next section, we will create a permanent Cloudflare Tunnel and connect it to a custom domain so that the website can be accessed using a user-friendly address such as:https://portfolio.example.com.np

Using a permanent tunnel with a custom domain provides better reliability, easier management, and automatic HTTPS encryption for secure communication.

Step 5: Add Your Domain to Cloudflare

After obtaining an active domain, the next step is to add it to your Cloudflare account so that Cloudflare can manage its DNS records and provide Tunnel services.

  1. Sign in to your Cloudflare account.

  2. From the dashboard, click Add a Domain.

  3. Enter your registered domain name (for example, yourdomain.com.np) and click Continue.

  4. Select the Free Plan, which provides DNS management, SSL certificates, and Cloudflare Tunnel support.

  5. Click Continue to proceed.

After the setup is completed, navigate to:

Domain → Overview

You will see the status of your domain. Initially, the domain may show Pending, but after updating the nameservers successfully, the status will change to Active.

Update the Domain Nameservers

Cloudflare will provide two nameservers, for example:

alice.ns.cloudflare.com

bob.ns.cloudflare.com

Go to your domain registrar’s management panel and replace the existing nameservers with the nameservers provided by Cloudflare.

After saving the changes, DNS propagation may take some time. Once the process is complete, the domain status in Cloudflare will change to Active.

Step 6: Authenticate Cloudflared with Your Cloudflare Account

After your domain becomes active, authenticate Cloudflared with your Cloudflare account by running:

cloudflared tunnel login

This command opens a browser window and asks you to log in to your Cloudflare account.

After logging in:

  1. Select the domain you added to Cloudflare.

  2. Click Authorize.

  3. Cloudflared will download a certificate file and store it locally.

  4. This certificate allows Cloudflared to create and manage tunnels associated with your domain.

Once the authentication is completed successfully, you are ready to create a permanent Cloudflare Tunnel and map it to your custom domain.

Step 6: Authenticate Cloudflared and Generate the Certificate File

To allow Cloudflared to create and manage tunnels for your domain, you need to authenticate it with your Cloudflare account.

Run the following command:

cloudflared tunnel login

After executing the command, a browser window will automatically open and redirect you to the Cloudflare login page.

  1. Log in to your Cloudflare account.

  2. Select the domain that you previously added to Cloudflare.

  3. Click Authorize to grant Cloudflared permission to manage tunnels for that domain.

After successful authorization, Cloudflared downloads an authentication certificate named:

cert.pem

This certificate is usually stored inside the .cloudflared directory in your user’s home folder.

For example:

Windows: C:\Users\<Username>.cloudflared\cert.pem

Linux/macOS: ~/.cloudflared/cert.pem

The cert.pem file is very important because it authenticates your local machine with your Cloudflare account. Cloudflared uses this certificate to create, configure, and manage tunnels associated with your domain.

You can verify that the certificate was created successfully by checking the .cloudflared directory. If the cert.pem file is present, the login process has been completed successfully, and you are ready to create a permanent Cloudflare Tunnel in the next step.

Step 7: Create a Permanent Cloudflare Tunnel

After successfully authenticating Cloudflared and obtaining the cert.pem file, you can create a permanent tunnel.

Run the following command:

cloudflared tunnel create classtunnel

Here, classtunnel is the name of the tunnel. After the command executes successfully, Cloudflared generates a Tunnel ID and stores a credentials file inside the .cloudflared directory.

For example:

~/.cloudflared/

├── cert.pem

└── <Tunnel-ID>.json

The cert.pem file is used for authentication, whereas the JSON file contains the credentials required to run the tunnel.

Create a DNS Route for the Tunnel

Next, create a DNS record that maps your custom domain to the Cloudflare Tunnel.

Run the following command:

cloudflared tunnel route dns classtunnel aakriti.info.np

Where:

· classtunnel is the name of the tunnel you created.

· aakriti.info.np is the custom domain that will point to your local application.

After the command is executed successfully, Cloudflare automatically creates a CNAME DNS record for the domain.

You can verify this by navigating to:

Cloudflare Dashboard → DNS

You will see a DNS entry similar to:

· Type: CNAME

· Name: aakriti.info.np

· Content: Automatically managed by Cloudflare Tunnel

· Proxy Status: Proxied

· TTL: Auto

The DNS record confirms that your custom domain is now connected to the Cloudflare Tunnel and is ready to serve your local application securely over the internet.

The figure below shows the DNS record created automatically after running the tunnel DNS routing command.

Figure: DNS record created for aakriti.info.np after configuring Cloudflare Tunnel.

Step 8: Create the config.yml File

After creating the tunnel and configuring the DNS record, the next step is to create a configuration file named config.yml inside the .cloudflared directory.

This file defines the tunnel name, the credentials file, and the local service that will be exposed to the internet.

Create the file:

~/.cloudflared/config.yml

Add the following configuration:

tunnel: classtunnel

credentials-file: C:\Users\<Username>.cloudflared\<Tunnel-ID>.json

ingress:

  • hostname: aakriti.info.np

service: http://localhost:8080

  • service: http_status:404

Explanation of the Configuration

· tunnel: Specifies the name of the Cloudflare Tunnel created earlier.

· tunnel: classtunnel

· credentials-file: Specifies the path to the tunnel credentials JSON file generated when the tunnel was created.

· credentials-file: C:\Users\<Username>.cloudflared\<Tunnel-ID>.json

· hostname: Defines the public domain that users will use to access the application.

· hostname: aakriti.info.np

· service: Specifies the local application running on port 8080.

· service: http://localhost:8080

· http_status:404: Acts as a fallback rule. If a request does not match any hostname, Cloudflare returns a 404 Not Found response.

· — service: http_status:404

After saving the config.yml file, Cloudflared knows:

· Which tunnel to use.

· Which credentials file to authenticate with.

· Which domain name to expose.

· Which local service to forward requests to.

The tunnel is now fully configured and ready to be started in the next step.

Step 9: Configure Cloudflare Tunnel as a Systemd Service

To ensure that the Cloudflare Tunnel starts automatically whenever the server reboots, create a Systemd service for Cloudflared.

Create the following service file:

sudo nano /etc/systemd/system/cloudflared-aakriti.service

Add the following configuration:

[Unit]

Description=Cloudflare Tunnel for aakriti

After=network.target

[Service]

Type=simple

User=a4kriti

Environment=HOME=/home/a4kriti

ExecStart=/usr/bin/cloudflared tunnel run aakriti

Restart=on-failure

RestartSec=5

[Install]

WantedBy=multi-user.target

Explanation

· Description: Describes the service.

· After=network.target: Starts the tunnel only after the network is available.

· User: Specifies the Linux user that runs the Cloudflare Tunnel.

· Environment: Sets the home directory where the .cloudflared folder is located.

· ExecStart: Runs the tunnel named aakriti.

· Restart=on-failure: Automatically restarts the tunnel if it crashes.

· RestartSec=5: Waits 5 seconds before restarting.

Step 10: Verify the Docker Container

Before starting the tunnel service, ensure that your Docker container is running.

List all containers:

sudo docker ps -a

If the portfolio container is stopped, start it:

sudo docker start portfolio

Step 11: Manage the Cloudflared Service

Navigate to the Systemd service directory:

cd /etc/systemd/system

List the files:

ls

View the contents of the service file:

cat cloudflared-aakriti.service

Reload the Systemd daemon to recognize the new service:

sudo systemctl daemon-reload

Start the Cloudflare Tunnel service:

sudo systemctl start cloudflared-aakriti.service

Check the service status:

sudo systemctl status cloudflared-aakriti.service

If the service is running correctly, you should see the status:

Active: active (running)

Enable the service so that it starts automatically after every system reboot:

sudo systemctl enable cloudflared-aakriti.service

Finally, verify that the service is enabled:

systemctl is-enabled cloudflared-aakriti.service

Expected output:

enabled

Once the service is enabled and running, the Cloudflare Tunnel will automatically start whenever the server boots, and your Dockerized portfolio website will remain accessible through your custom domain:

https://aakriti.info.np

This completes the deployment of a local Dockerized portfolio website to the internet using Cloudflare Tunnel with a custom domain and automatic service management through Systemd.

Step 12: Push the Portfolio Project to GitHub

After successfully deploying the portfolio website using Docker and Cloudflare Tunnel, it is a good practice to store the project in a GitHub repository for version control and backup.

1. Generate an SSH Key

First, generate an SSH key on your Linux machine:

ssh-keygen -t ed25519 -C “your-email@example.com”

Press Enter to save the key in the default location:

~/.ssh/id_ed25519

This command generates:

· Private Key: id_ed25519

· Public Key: id_ed25519.pub

2. Add the SSH Public Key to GitHub

Display the public key:

cat ~/.ssh/id_ed25519.pub

Copy the output and log in to your GitHub account.

Navigate to:

Settings → SSH and GPG Keys → New SSH Key

Paste the copied key and save it.

3. Test the SSH Connection

Verify that your machine can communicate with GitHub:

ssh -T git@github.com

If the authentication is successful, you will see a message similar to:

Hi username! You’ve successfully authenticated, but GitHub does not provide shell access.

4. Initialize the Git Repository

Move to your portfolio project directory:

cd ~/portfolio-site

Initialize Git:

git init

5. Configure Git Username and Email

Configure your Git identity:

git config — global user.name “Your GitHub Username”

git config — global user.email “your-email@example.com”

You can verify the configuration using:

git config — list

6. Add Project Files

Add all files to the staging area:

git add .

Check the status:

git status

7. Commit the Changes

Create the first commit:

git commit -m “First commit”

8. Add the Remote Repository

Create an empty repository on GitHub and copy its SSH URL.

Add the remote repository:

git remote add origin git@github.com:username/portfolio-site.git

Verify the remote URL:

git remote -v

Example output:

origin git@github.com:username/portfolio-site.git (fetch)

origin git@github.com:username/portfolio-site.git (push)

9. Push the Project to GitHub

Push the project to the main branch:

git push -u origin main

The -u option sets the upstream branch, allowing future pushes to be performed simply with:

git push

10. Check Repository Status

At any time, you can check the repository status using:

git status

If all files are committed and pushed successfully, Git displays:

On branch main

Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

Your portfolio project is now securely stored on GitHub, making it easy to track changes, collaborate with others, and deploy updates in the future.

Step 13: Configure GitHub Actions with a Self-Hosted Runner

After pushing the portfolio project to GitHub, the next step is to automate the deployment process using GitHub Actions and a Self-Hosted Runner.

1. Add Secrets and Variables in GitHub

The SSH credentials created earlier will be required by GitHub Actions to connect to the server.

Navigate to:

Repository → Settings → Secrets and Variables → Actions

Add the following secrets:

· HOST — IP address or hostname of your server.

· USER — Username of the server.

· SSH_KEY — Private SSH key used to connect to the server.

These secrets are securely stored by GitHub and can be used inside workflow files.

2. Configure a Self-Hosted Runner

Go to:

Repository → Settings → Actions → Runners

Click:

New self-hosted runner

Choose:

Operating System: Linux

GitHub will display a series of commands similar to:

mkdir actions-runner && cd actions-runner

curl -o actions-runner-linux-x64.tar.gz -L <download-url>

tar xzf ./actions-runner-linux-x64.tar.gz

./config.sh — url https://github.com/username/portfolio-site — token <token>

./run.sh

Run these commands one by one on your Linux server.

After the configuration is completed, the runner will register itself with your GitHub repository.

3. Create the GitHub Workflow

Inside your project, create the following directory:

.github/workflows/

Create a workflow file named:

deploy.yml

Example:

name: Deploy Portfolio

on:

push:

branches:

  • main

jobs:

deploy:

runs-on: self-hosted

steps:

  • name: Checkout Repository

uses: actions/checkout@v4

  • name: Pull Latest Code

run: |

cd /home/a4kriti/portfolio-site

git pull origin main

  • name: Restart Docker Container

run: |

docker compose down

docker compose up -d — build

This workflow automatically runs whenever code is pushed to the main branch.

4. Commit and Push the Workflow

Add the workflow file:

git add .

Commit the changes:

git commit -m “Add GitHub Actions deployment workflow”

Push the changes:

git push

5. Verify the Self-Hosted Runner

After pushing the code, navigate to:

Repository → Actions

You will see the workflow execution in progress.

Also, go to:

Repository → Settings → Actions → Runners

If the runner is connected successfully, its status will appear as:

Idle

When a workflow is running:

Active

This indicates that the Self-Hosted Runner is successfully connected to the repository and is capable of automatically deploying updates whenever new code is pushed to GitHub.

With this setup, every change pushed to the main branch can be automatically deployed to your server without requiring manual intervention.

Step 14: Collaborating with Team Members Using Git and GitHub

Git and GitHub make it easy for multiple developers to collaborate on the same project simultaneously. In this project, two team members can work independently on different features using separate branches and later merge their changes through Pull Requests.

1. Clone the Repository

First, each team member clones the repository from GitHub to their local machine.

git clone git@github.com:username/portfolio-site.git

Move into the project directory:

cd portfolio-site

2. Create and Switch to a New Branch

Each collaborator should create a separate branch for their work.

git checkout -b feature-branch

For example:

git checkout -b update-homepage

This creates a new branch and switches to it immediately.

3. Make Changes and Add Files

After modifying the project files, add the changes to the staging area:

git add .

Check the status:

git status

4. Commit the Changes

Commit the modifications with a descriptive message:

git commit -m “Update homepage design”

5. Push the Branch to GitHub

Push the newly created branch to GitHub:

git push origin update-homepage

The branch will now be visible in the GitHub repository.

6. Compare and Create a Pull Request

On GitHub:

  1. Open the repository.

  2. Navigate to Pull Requests.

  3. Click New Pull Request.

  4. Compare the feature branch with the main branch.

  5. Review the changes.

  6. Click Create Pull Request.

The other team member can review the code, provide comments, and approve the changes.

7. Merge the Pull Request

After approval, click:

Merge Pull Request

Then:

Confirm Merge

The changes from the feature branch will be merged into the main branch.

8. Automatic CI/CD Pipeline Execution

Once the Pull Request is merged into the main branch, the GitHub Actions workflow is triggered automatically.

The CI/CD pipeline performs tasks such as:

· Checking out the latest code.

· Pulling updated files to the server.

· Building the Docker image.

· Restarting Docker containers.

· Deploying the latest version of the portfolio website.

You can monitor the pipeline execution by navigating to:

Repository → Actions

If the Self-Hosted Runner is connected properly, its status will change from:

Idle

to:

Active

while the workflow is running.

After the deployment completes successfully, the updated portfolio website becomes available automatically through the custom domain without requiring any manual deployment steps.

This collaborative workflow enables multiple developers to work on the same project efficiently while ensuring that every approved change is automatically built and deployed through the CI/CD pipeline.

“In this blog, we successfully deployed a Dockerized portfolio website using Cloudflare Tunnel and a custom domain. We automated deployments with GitHub Actions and a Self-Hosted Runner, enabling secure, scalable, and collaborative application deployment with CI/CD.”


메타데이터
post_id
ce7fd2e6b31b
slug
from-code-to-cloud-how-i-deployed-my-portfolio-with-a-custom-domain-github-collaboration-ci-cd-ce7fd2e6b31b
url
https://medium.com/@krtbastola/from-code-to-cloud-how-i-deployed-my-portfolio-with-a-custom-domain-github-collaboration-ci-cd-ce7fd2e6b31b
canonical_url
https://medium.com/@krtbastola/from-code-to-cloud-how-i-deployed-my-portfolio-with-a-custom-domain-github-collaboration-ci-cd-ce7fd2e6b31b
author_url
https://medium.com/@krtbastola
status
ok
fetched_at
2026-06-20 20:29:01