Building a GitOps Pipeline with Self-Hosted GitLab and Argo CD Across VPCs on AWS

Introduction

In modern DevOps, GitOps has become a standard way to deploy applications.
Instead of manually deploying code, we use Git as the single source of truth, and tools like Argo CD automatically deploy changes to Kubernetes.

In this project, I built a real-world GitOps architecture using:

  • Self-hosted GitLab on AWS EC2
  • Argo CD running on a Kubernetes cluster
  • Secure SSH-based repository access
  • VPC peering between two separate environments

This setup simulates how real organizations separate source control and deployment infrastructure across networks.

Tools and Technologies Used

  • AWS EC2
  • Amazon VPC Peering
  • Docker
  • GitLab CE
  • Kubernetes
  • Helm
  • Argo CD
  • SSH-based authentication

Step 1: Launch GitLab EC2 Instance

Instance details
SettingValue
AMIAmazon Linux
Instance typet2.medium
Storage30 GB
Ports22, 80, 443, 2222

Step 2: Install Docker on GitLab EC2

sudo dnf update -y
sudo dnf install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ec2-user

Logout and login again.


Step 3: Create GitLab Directory

sudo mkdir -p /srv/gitlab
sudo mkdir -p /srv/gitlab/config
sudo mkdir -p /srv/gitlab/logs
sudo mkdir -p /srv/gitlab/data

Set permissions:

sudo chown -R ec2-user:ec2-user /srv/gitlab



Step 4: Run GitLab container

docker run -d \
  --hostname ims.work.gd \
  -p 80:80 \
  -p 443:443 \
  -p 2222:22 \
  --name gitlab \
  --restart always \
  -v /srv/gitlab/config:/etc/gitlab \
  -v /srv/gitlab/logs:/var/log/gitlab \
  -v /srv/gitlab/data:/var/opt/gitlab \
  gitlab/gitlab-ce:latest

Wait 5–10 minutes.

Step 5: Get GitLab root password

docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

Login:

http://<EC2-PUBLIC-IP>
Username: root
Password: <password>

Step 5.1: Reset GitLab Root Password (If Login Fails)

Sometimes the initial root password may not work due to:

  • Password expiration
  • Container restart
  • Copy–paste errors
  • First-time setup issues

In that case, you can reset the root password directly inside the GitLab container.


Step A) Enter the GitLab container
docker exec -it gitlab bash

Step B) Open GitLab Rails console

Inside the container:

gitlab-rails console

Wait 1–2 minutes for the console to load.


Step C) Reset the root password

Run the following commands:

user = User.find_by_username('root')
user.password = 'NewPassword@123'
user.password_confirmation = 'NewPassword@123'
user.save!

If successful, you will see:

=> true

Step D) Exit the console

Type:

exit

Then exit the container:

exit

Step 6: Configure domain

Edit config:

sudo nano /srv/gitlab/config/gitlab.rb

Set:

external_url 'http://ims.work.gd'

Apply config:

docker exec -it gitlab gitlab-ctl reconfigure
docker restart gitlab

Now access:

http://ims.work.gd

Step 7: Launch Argo CD EC2 (different VPC)

SettingValue
AMIUbuntu
Instancet2.medium
VPCSeparate from GitLab

Install Kubernetes (example using kind or k3s).


Step 8: Install Argo CD via Helm

Add Helm repo
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
Create namespace
kubectl create namespace argocd
Install Argo CD
helm install argocd argo/argo-cd -n argocd

Step 9: Access Argo CD UI

Port forward:

kubectl port-forward svc/argocd-server -n argocd 8080:443

Open:

https://localhost:8080

Step 10: Get Argo CD admin password

kubectl get secret argocd-initial-admin-secret \
-n argocd \
-o jsonpath="{.data.password}" | base64 -d

Login:

Username: admin
Password: <password>

Step 11: Configure VPC Peering

Example CIDRs
VPCCIDR
GitLab VPC172.31.0.0/16
Argo CD VPC10.0.0.0/16

Route table entries
Argo CD VPC route table
DestinationTarget
172.31.0.0/16pcx-id
GitLab VPC route table
DestinationTarget
10.0.0.0/16pcx-id

Security group rule (GitLab)

Allow SSH from Argo CD VPC:

TypePortSource
TCP222210.0.0.0/16

Step 12: Generate SSH key on Argo CD server

ssh-keygen -t ed25519 -C "argocd" -f argocd_gitlab

Step 13: Add SSH key to GitLab

On GitLab:

Profile → SSH Keys

Add:

cat argocd_gitlab.pub

Step 14: Test SSH from Argo CD server

ssh -i argocd_gitlab -T git@ims.work.gd -p 2222

Expected:


Step 15: Install Argo CD CLI

curl -sSL -o argocd \
https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64

chmod +x argocd
sudo mv argocd /usr/local/bin/

Step 16: Login to Argo CD CLI

Start port-forward:

kubectl port-forward svc/argocd-server -n argocd 8080:443

In another terminal:

argocd login localhost:8080 --username admin --password <password> --insecure

Step 17: Add GitLab host key to Argo CD

ssh-keyscan -p 2222 ims.work.gd | \
kubectl create configmap argocd-ssh-known-hosts-cm \
-n argocd \
--from-file=ssh_known_hosts=/dev/stdin \
--dry-run=client -o yaml | kubectl apply -f -

Restart repo server:

kubectl rollout restart deployment argocd-repo-server -n argocd

Step 18: Add repository to Argo CD

argocd repo add ssh://git@ims.work.gd:2222/flask/loginflask.git \
--ssh-private-key-path argocd_gitlab

Verify:

argocd repo list

Expected:

STATUS: Successful

Step 19: Create Argo CD application

Example:

argocd app create flask-app \
  --repo ssh://git@ims.work.gd:2222/flask/loginflask.git \
  --path k8s \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default

Sync:

argocd app sync flask-app

Final Result

You now have:

  • Self-hosted GitLab on EC2
  • Argo CD in another VPC
  • VPC peering between environments
  • SSH-based secure repo access

Conclusion

This project demonstrates a complete, production-style GitOps pipeline on AWS using self-hosted GitLab and Argo CD across multiple VPCs. By separating source control and deployment infrastructure, the setup closely mirrors how real organizations design secure and scalable DevOps environments.

Through this implementation, GitLab acts as the single source of truth, while Argo CD continuously monitors the repository and automatically syncs application changes to Kubernetes. SSH-based authentication ensures secure repository access, and VPC peering enables controlled, private communication between isolated environments without exposing services publicly.

Overall, this architecture highlights the core benefits of GitOps: automation, consistency, security, and traceability. It provides a strong foundation that can be further extended with features like RBAC, secrets management, multi-cluster deployments, monitoring, and CI integration—making it suitable for both learning and real-world production use.