From code to cloud: How I deployed MY Portfolio with a custom domain, Github collaboration &CI/CD.
Introduction
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:
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:
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.
-
Sign in to your Cloudflare account.
-
From the dashboard, click Add a Domain.
-
Enter your registered domain name (for example, yourdomain.com.np) and click Continue.
-
Select the Free Plan, which provides DNS management, SSL certificates, and Cloudflare Tunnel support.
-
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:
-
Select the domain you added to Cloudflare.
-
Click Authorize.
-
Cloudflared will download a certificate file and store it locally.
-
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.
-
Log in to your Cloudflare account.
-
Select the domain that you previously added to Cloudflare.
-
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:
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:
-
Open the repository.
-
Navigate to Pull Requests.
-
Click New Pull Request.
-
Compare the feature branch with the main branch.
-
Review the changes.
-
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