BogoToBogo
  • Home
  • About
  • Big Data
  • Machine Learning
  • AngularJS
  • Python
  • C++
  • go
  • DevOps
  • Kubernetes
  • Algorithms
  • More...
    • Qt 5
    • Linux
    • FFmpeg
    • Matlab
    • Django 1.8
    • Ruby On Rails
    • HTML5 & CSS

MEAN Stack app on Docker containers : micro services

MEAN-Icon.png




Bookmark and Share





bogotobogo.com site search:




Introduction

In this tutorial, we'll deploy MEAN application to two Docker containers, and our local machine will be hosting the two containers:

  1. mongodb container
  2. node/express/angular app

Here is the home page of the app:

akaML-HomePage.png

Source code : Github

The app uses passport for user authentication, and for more features, please consult the README.md of the repo.







Local nginx for testing app

Though we don't need Nginx server in the Docker work flow of this tutorial, before we deploy our app to Docker containers, we may want to test it with Nginx. The configuration file looks like this:

server {
    listen 80;
    server_name akaml.com;

    location / {
        proxy_pass http://localhost:3000;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

With /etc/hosts:

127.0.0.1 akaml.com

To run our MEAN app on host machine but not on container, the following line in config/database.js should be modified like this instead of "container's ip":

var dbURI = 'mongodb://localhost:27017/myApp';

Also, mongodb should be running before MEAN app's run:

$ sudo service mongodb restart

In the project folder, install packages:

$ npm install 
$ sudo npm install -g bower
$ bower install

Then start Nginx proxy server:

$ sudo service nginx start

Now, we're ready to run MEAN app using the Nginx as a reverse proxy configuration:

$ node server.js

Or

$ nodemon server.js

Or

$ pm2 start server.js

Apache config is also available from the repo.





MongoDB docker

To get our application running, the MongoDB container needs be started first.

We'll use OFFICIAL REPOSITORY : mongo.

$ docker run [-p 27017:27017] --name mymongodb -d mongo
  1. -p 27017:27017 exposes the MongoDB port so the mean container can connect to it.
  2. -d runs it as a background process (detached mode).
  3. --name mymongodb gives this container a name so it can be referenced.
  4. mongo is the image name that should be run.

Our mongo will be listening on 27017 port by default and that's it. We don't have to do anything once we launched the container.

$ docker ps
CONTAINER ID   IMAGE  COMMAND                  CREATED         STATUS         PORTS       NAMES
8f333617ed15   mongo  "/entrypoint.sh mongo"   4 minutes ago   Up 3 minutes   27017/tcp   mymongodb




MEAN stack on docker

Even though we'll build our container starting from a simple ubuntu:14.04 images and make it ready for MEAN app, we may use the ready-made image of MEANJS (https://hub.docker.com/r/maccam912/meanjs/).


If maccam912/meanjs is chosen, we can run the container with the following command:

$ docker run -i -t --name mymeanjs --link mymongodb:db_1 -p 80:3000 maccam912/meanjs:latest bash 

Then, we can skip this section.


$ docker run -i -t --name mymeanjs --link mymongodb:db_1 -p 80:3000 ubuntu:14.04 bash 

Here, the --link mymongodb:db_1 argument is a link between this mymeanjs container and mymongodb container. This is a docker's way of communicating between containers.

db_1 is an alias to reference this connected container. In other words, our MEAN application is set to use db_1.

By the argument of -p 80:3000, we're mapping the 3000 container port to 80 host machine port. Our MEAN application is set to run on port 3000, and the mapping enables any request on http:80 from outside the container to access our app running deep inside our container.

To install MEAN, we need to work within the docker container and do the following:

# apt-get update
# apt-get install nodejs
# ln -s "$(which nodejs)" /usr/bin/node
# apt-get install npm

To check if our install:

# node -v
v0.10.25
# npm -v
1.3.10

Express & bower install:

# npm install -g express
# npm install -g bower




Get our MEAN app

Install git:

# apt-get install git

Clone our repo to get the source code:

# cd home
# git clone https://github.com/Einsteinish/akaML.git
# cd akaML

On our MEAN.JS folder, download all the package dependencies:

# npm install

Install the front-end dependencies running by running bower:

# bower install --allow-root




Run our MEAN app

Make sure our mongodb ip is correct in config/database.js:

var dbURI = 'mongodb://172.17.0.2:27017/myApp';

We can check it by issuing the following command on our host machine, and this will give us two ips:

172.17.0.3
172.17.0.2

On our MEAN container, we can check ip via "ifconfig":

inet addr:172.17.0.3

So, the ip for mongodb is '172.17.0.2', and our database configuration is correct!

Let's run our MEAN app:

root@bcd9985dc871:/home/akaML# node server.js
Express server listening on port : 3000
Mongoose connection open to mongodb://172.17.0.2:27017/myApp

GET / 304 71ms
GET /scripts/lib/angular-toastr/dist/angular-toastr.css 200 95ms - 6.64kb
GET /scripts/lib/bootstrap/dist/css/bootstrap.css 200 112ms - 142.59kb
GET /css/app.css 200 226ms - 1.32kb
GET /scripts/lib/requirejs/require.js 200 186ms - 84.24kb
GET /scripts/src/main.js 200 38ms - 1.46kb
GET /favicon.ico 404 5ms
GET /scripts/src/app.js 200 5ms - 3.89kb
GET /scripts/lib/jquery/dist/jquery.min.js 200 31ms - 84.33kb
GET /scripts/lib/angular/angular.min.js 200 37ms - 156.3kb
GET /scripts/src/controllers.js 200 5ms - 7.39kb
GET /scripts/lib/cryptojslib/rollups/pbkdf2.js 200 68ms - 5.4kb
GET /scripts/src/services.js 200 45ms - 4.23kb
GET /scripts/lib/bootstrap/dist/js/bootstrap.min.js 200 41ms - 36.18kb
GET /scripts/lib/angular-route/angular-route.min.js 200 26ms - 4.65kb
GET /scripts/lib/angular-animate/angular-animate.min.js 200 26ms - 25.11kb
GET /scripts/lib/angular-local-storage/dist/angular-local-storage.min.js 200 44ms - 6.25kb
GET /scripts/lib/angular-toastr/dist/angular-toastr.tpls.min.js 200 18ms - 7.02kb
{ REQUEST: 
   { HEADERS: 
      { host: 'akaml.com',
        connection: 'keep-alive',
        accept: 'application/json, text/plain, */*',
        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
        referer: 'http://akaml.com/',
        'accept-encoding': 'gzip, deflate, sdch',
        'accept-language': 'en-US,en;q=0.8',
        'if-none-match': '"1892644392"' },
     BODY: {} } }
GET /partials/login 304 35ms
{ REQUEST: 
   { HEADERS: 
      { host: 'akaml.com',
        connection: 'keep-alive',
        accept: 'application/json, text/plain, */*',
        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
        referer: 'http://akaml.com/',
        'accept-encoding': 'gzip, deflate, sdch',
        'accept-language': 'en-US,en;q=0.8',
        'if-none-match': '"1227391045"' },
     BODY: {} } }
GET /partials/nav.html 304 8ms
{ REQUEST: 
   { HEADERS: 
      { host: 'akaml.com',
        connection: 'keep-alive',
        accept: 'application/json, text/plain, */*',
        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
        referer: 'http://akaml.com/',
        'accept-encoding': 'gzip, deflate, sdch',
        'accept-language': 'en-US,en;q=0.8' },
     BODY: {} } }
GET /partials/header.html 200 5ms - 170b
GET /img/home/akaML-home.jpg 200 28ms - 82.08kb
GET /scripts/src/main.js 304 3ms
GET /scripts/src/app.js 304 5ms
GET /scripts/lib/angular/angular.min.js 304 2ms
GET /scripts/src/controllers.js 304 4ms
GET /scripts/src/services.js 304 1ms
GET /scripts/lib/cryptojslib/rollups/pbkdf2.js 304 2ms
GET /scripts/lib/bootstrap/dist/js/bootstrap.min.js 304 2ms
GET /scripts/lib/angular-route/angular-route.min.js 304 4ms
GET /scripts/lib/angular-animate/angular-animate.min.js 304 1ms
GET /scripts/lib/angular-local-storage/dist/angular-local-storage.min.js 304 3ms
GET /scripts/lib/angular-toastr/dist/angular-toastr.tpls.min.js 304 3ms
GET /scripts/lib/requirejs/require.js 304 2ms

At this point, we'll get same the page in the "Introduction" section of this tutorial. After Registration/Login, we will get the following:

akaML-after-login.png



Optional : Run node server with PM2

We may want to run node server as a daemon using PM2.

# npm install pm2 -g

Now that the PM2 is installed, let's start our Node application:

# pm2 start server.js

Then, we get the following screen output:

PM2-MEAN-Docker.png

To stop the server:

# pm2 stop server.js
PM2-Stop-MEAN-Docker.png.png



Optional : Run node server with nodemon

The PM2 has relatively bigger footprints, so as an alternative we can use nodemon.

# npm install -g nodemon
# nodemon server.js




Optional : Saving the docker image

We want to save our work (layers in docker terminology) so far.

Let's exit the NodeJS app container:

root@3d1f65885382:/home/akaML# exit
$

Then, check which containers are running:

$ docker ps
CONTAINER ID  IMAGE COMMAND                 CREATED      STATUS      PORTS      NAMES
8f333617ed15  mongo "/entrypoint.sh mongo"  2 hours ago  Up 2 hours  27017/tcp  mymongodb

Since we exited from the NodeJS container, only the mongodb container is running.

To check our recent containers, we can use docker ps -a command:

$ docker ps -a
CONTAINER ID  IMAGE                     COMMAND  CREATED      STATUS                   PORTS  NAMES
3d1f65885382  maccam912/meanjs:latest   "bash"   2 hours ago  Exited (0) 4 minutes ago        mymeanjs

"docker commit" is the command we want to use to save the image:

$ docker commit -a khong 3d1f65885382 nodejs-micro-service:0.1
581298efda9f2874ed86f90f47b4834c6eb863650cdf4e3b764d939ece1cfd31

Here "-a" flag is for the author, "3d1f65885382" is the container id, and after that we specified the name (repository) of the image with version tag.

Let's check we really create a new image:

$ docker images
REPOSITORY            TAG     IMAGE ID            CREATED             VIRTUAL SIZE
nodejs-micro-service  0.1     581298efda9f        3 minutes ago       1.116 GB

Let's run our newly created image in background:

$ docker run -d -p 80:3000 nodejs-micro-service:0.1 pm2 start /home/akaML/server.js
8d3eb5ab0d89d65243b29ff06ccc781bca9c3698ebaa791ac1a4f8ae3a92a76d

Here, the "-d" flag is "detached" meaning background run, "80:3000" is port forwarding, "nodejs-micro-service:0.1" is the container name, and "node /home/akaML/server.js" is the command to run our nodejs app.


Now we should have two running containers - mongo and nodejs

$ docker ps
CONTAINER ID  IMAGE                     COMMAND                 CREATED       STATUS       PORTS                                   NAMES
0af14aa1f952  nodejs-micro-service:0.1  "node /home/akaML/ser"  8 seconds ago Up 5 seconds 80/tcp, 443/tcp, 0.0.0.0:80->3000/tcp   evil_hugle
8f333617ed15  mongo                     "/entrypoint.sh mongo"  8 hours ago   Up 8 hours   27017/tcp                               mymongodb

We can use "docker attach container-id" command to see what's going on inside our containers:

$ docker attach 0af14aa1f952
GET / 304 64ms
GET /css/app.css 304 16ms
...

Note: Unfortunately, the command "pm2 start /home/akaML/server.js" in detached mode does not seem to be working. So, here, I used "node server.js" as a command argument.
















Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization

YouTubeMy YouTube channel

Sponsor Open Source development activities and free contents for everyone.

Thank you.

- K Hong







Node.JS



Node.js

MEAN Stack : MongoDB, Express.js, AngularJS, Node.js

MEAN Stack Tutorial : Express.js with Jade template

Building REST API with Node and MongoDB

Nginx reverse proxy to a node application server managed by PM2

Jade Bootstrap sample page with Mixins

Real-time polls application I - Express, Jade template, and AngularJS modules/directives

Real-time polls application II - AngularJS partial HTML templates & style.css

Node ToDo List App with Mongodb

Node ToDo List App with Mongodb - II (more Angular)

Authentication with Passport

Authentication with Passport 2

Authentication with Passport 3 (Facebook / Twitter Login)

React Starter Kit

Meteor app with React

MEAN Stack app on Docker containers : micro services

MEAN Stack app on Docker containers : micro services via docker-compose




Sponsor Open Source development activities and free contents for everyone.

Thank you.

- K Hong







AngularJS



Introduction

Directives I - ng-app, ng-model, and ng-bind

Directives II - ng-show, ng-hide, and ng-disabled

Directives III - ng-click with toggle()

Expressions - numbers, strings, and arrays

Binding - ng-app, ng-model, and ng-bind

Controllers - global controllers, controller method, and external controllers

Data Binding and Controllers (Todo App)

Todo App with Node

$scope - A glue between javascript (controllers) and HTML (the view)

Tables and css

Dependency Injection - http:fetch json & minification

Filters - lower/uppercase, currenty, orderBy, and filter:query with http.get()

$http - XMLHttpRequest and json file

Module - module file and controller file

Forms

Routes I - introduction

Routes II - separate url template files

Routes III - extracting and using parameters from routes

Routes IV - navigation between views using links

Routes V - details page

AngularJS template using ng-view directive : multiple views

Nested and multi-views using UI-router, ngRoute vs UI-router

Creating a new service using factory

Querying into a service using find()

angular-seed - the seed for AngularJS apps

Token (JSON Web Token - JWT) based auth backend with NodeJS

Token (JSON Web Token - JWT) based auth frontend with AngularJS

Twitter Bootstrap

Online resources - List of samples using AngularJS (Already launched sites and projects)

Meteor Angular App with MongoDB (Part I)

Meteor Angular App with MongoDB (Part II - Angular talks with MongoDB)

Meteor Angular App with MongoDB (Part III - Facebook / Twitter / Google logins)

Scala/Java Play app with Angular

Laravel 5 / Angular Auth using JSON Web Token (JWT) - Prod

Scala/Java Play app with Angular







Docker & K8s



Docker install on Amazon Linux AMI

Docker install on EC2 Ubuntu 14.04

Docker container vs Virtual Machine

Docker install on Ubuntu 14.04

Docker Hello World Application

Nginx image - share/copy files, Dockerfile

Working with Docker images : brief introduction

Docker image and container via docker commands (search, pull, run, ps, restart, attach, and rm)

More on docker run command (docker run -it, docker run --rm, etc.)

Docker Networks - Bridge Driver Network

Docker Persistent Storage

File sharing between host and container (docker run -d -p -v)

Linking containers and volume for datastore

Dockerfile - Build Docker images automatically I - FROM, MAINTAINER, and build context

Dockerfile - Build Docker images automatically II - revisiting FROM, MAINTAINER, build context, and caching

Dockerfile - Build Docker images automatically III - RUN

Dockerfile - Build Docker images automatically IV - CMD

Dockerfile - Build Docker images automatically V - WORKDIR, ENV, ADD, and ENTRYPOINT

Docker - Apache Tomcat

Docker - NodeJS

Docker - NodeJS with hostname

Docker Compose - NodeJS with MongoDB

Docker - Prometheus and Grafana with Docker-compose

Docker - StatsD/Graphite/Grafana

Docker - Deploying a Java EE JBoss/WildFly Application on AWS Elastic Beanstalk Using Docker Containers

Docker : NodeJS with GCP Kubernetes Engine

Docker : Jenkins Multibranch Pipeline with Jenkinsfile and Github

Docker : Jenkins Master and Slave

Docker - ELK : ElasticSearch, Logstash, and Kibana

Docker - ELK 7.6 : Elasticsearch on Centos 7 Docker - ELK 7.6 : Filebeat on Centos 7

Docker - ELK 7.6 : Logstash on Centos 7

Docker - ELK 7.6 : Kibana on Centos 7 Part 1

Docker - ELK 7.6 : Kibana on Centos 7 Part 2

Docker - ELK 7.6 : Elastic Stack with Docker Compose

Docker - Deploy Elastic Cloud on Kubernetes (ECK) via Elasticsearch operator on minikube

Docker - Deploy Elastic Stack via Helm on minikube

Docker Compose - A gentle introduction with WordPress

Docker Compose - MySQL

MEAN Stack app on Docker containers : micro services

Docker Compose - Hashicorp's Vault and Consul Part A (install vault, unsealing, static secrets, and policies)

Docker Compose - Hashicorp's Vault and Consul Part B (EaaS, dynamic secrets, leases, and revocation)

Docker Compose - Hashicorp's Vault and Consul Part C (Consul)

Docker Compose with two containers - Flask REST API service container and an Apache server container

Docker compose : Nginx reverse proxy with multiple containers

Docker compose : Nginx reverse proxy with multiple containers

Docker & Kubernetes : Envoy - Getting started

Docker & Kubernetes : Envoy - Front Proxy

Docker & Kubernetes : Ambassador - Envoy API Gateway on Kubernetes

Docker Packer

Docker Cheat Sheet

Docker Q & A

Kubernetes Q & A - Part I

Kubernetes Q & A - Part II

Docker - Run a React app in a docker

Docker - Run a React app in a docker II (snapshot app with nginx)

Docker - NodeJS and MySQL app with React in a docker

Docker - Step by Step NodeJS and MySQL app with React - I

Installing LAMP via puppet on Docker

Docker install via Puppet

Nginx Docker install via Ansible

Apache Hadoop CDH 5.8 Install with QuickStarts Docker

Docker - Deploying Flask app to ECS

Docker Compose - Deploying WordPress to AWS

Docker - WordPress Deploy to ECS with Docker-Compose (ECS-CLI EC2 type)

Docker - ECS Fargate

Docker - AWS ECS service discovery with Flask and Redis

Docker & Kubernetes: minikube version: v1.31.2, 2023

Docker & Kubernetes 1 : minikube

Docker & Kubernetes 2 : minikube Django with Postgres - persistent volume

Docker & Kubernetes 3 : minikube Django with Redis and Celery

Docker & Kubernetes 4 : Django with RDS via AWS Kops

Docker & Kubernetes : Kops on AWS

Docker & Kubernetes : Ingress controller on AWS with Kops

Docker & Kubernetes : HashiCorp's Vault and Consul on minikube

Docker & Kubernetes : HashiCorp's Vault and Consul - Auto-unseal using Transit Secrets Engine

Docker & Kubernetes : Persistent Volumes & Persistent Volumes Claims - hostPath and annotations

Docker & Kubernetes : Persistent Volumes - Dynamic volume provisioning

Docker & Kubernetes : DaemonSet

Docker & Kubernetes : Secrets

Docker & Kubernetes : kubectl command

Docker & Kubernetes : Assign a Kubernetes Pod to a particular node in a Kubernetes cluster

Docker & Kubernetes : Configure a Pod to Use a ConfigMap

AWS : EKS (Elastic Container Service for Kubernetes)

Docker & Kubernetes : Run a React app in a minikube

Docker & Kubernetes : Minikube install on AWS EC2

Docker & Kubernetes : Cassandra with a StatefulSet

Docker & Kubernetes : Terraform and AWS EKS

Docker & Kubernetes : Pods and Service definitions

Docker & Kubernetes : Headless service and discovering pods

Docker & Kubernetes : Service IP and the Service Type

Docker & Kubernetes : Kubernetes DNS with Pods and Services

Docker & Kubernetes - Scaling and Updating application

Docker & Kubernetes : Horizontal pod autoscaler on minikubes

Docker & Kubernetes : NodePort vs LoadBalancer vs Ingress

Docker & Kubernetes : Load Testing with Locust on GCP Kubernetes

Docker & Kubernetes : From a monolithic app to micro services on GCP Kubernetes

Docker & Kubernetes : Rolling updates

Docker & Kubernetes : Deployments to GKE (Rolling update, Canary and Blue-green deployments)

Docker & Kubernetes : Slack Chat Bot with NodeJS on GCP Kubernetes

Docker & Kubernetes : Continuous Delivery with Jenkins Multibranch Pipeline for Dev, Canary, and Production Environments on GCP Kubernetes

Docker & Kubernetes - MongoDB with StatefulSets on GCP Kubernetes Engine

Docker & Kubernetes : Nginx Ingress Controller on minikube

Docker & Kubernetes : Setting up Ingress with NGINX Controller on Minikube (Mac)

Docker & Kubernetes : Nginx Ingress Controller for Dashboard service on Minikube

Docker & Kubernetes : Nginx Ingress Controller on GCP Kubernetes

Docker & Kubernetes : Kubernetes Ingress with AWS ALB Ingress Controller in EKS

Docker & Kubernetes : MongoDB / MongoExpress on Minikube

Docker & Kubernetes : Setting up a private cluster on GCP Kubernetes

Docker & Kubernetes : Kubernetes Namespaces (default, kube-public, kube-system) and switching namespaces (kubens)

Docker & Kubernetes : StatefulSets on minikube

Docker & Kubernetes : StatefulSets on minikube

Docker & Kubernetes : RBAC

Docker & Kubernetes Service Account, RBAC, and IAM

Docker & Kubernetes - Kubernetes Service Account, RBAC, IAM with EKS ALB, Part 1

Docker & Kubernetes : Helm Chart

Docker & Kubernetes : My first Helm deploy

Docker & Kubernetes : Readiness and Liveness Probes

Docker & Kubernetes : Helm chart repository with Github pages

Docker & Kubernetes : Deploying WordPress and MariaDB with Ingress to Minikube using Helm Chart

Docker & Kubernetes : Deploying WordPress and MariaDB to AWS using Helm 2 Chart

Docker & Kubernetes : Deploying WordPress and MariaDB to AWS using Helm 3 Chart

Docker & Kubernetes : Helm Chart for Node/Express and MySQL with Ingress

Docker & Kubernetes : Docker_Helm_Chart_Node_Expess_MySQL_Ingress.php

Docker & Kubernetes: Deploy Prometheus and Grafana using Helm and Prometheus Operator - Monitoring Kubernetes node resources out of the box

Docker & Kubernetes : Deploy Prometheus and Grafana using kube-prometheus-stack Helm Chart

Docker & Kubernetes : Istio (service mesh) sidecar proxy on GCP Kubernetes

Docker & Kubernetes : Istio on EKS

Docker & Kubernetes : Istio on Minikube with AWS EC2 for Bookinfo Application

Docker & Kubernetes : Deploying .NET Core app to Kubernetes Engine and configuring its traffic managed by Istio (Part I)

Docker & Kubernetes : Deploying .NET Core app to Kubernetes Engine and configuring its traffic managed by Istio (Part II - Prometheus, Grafana, pin a service, split traffic, and inject faults)

Docker & Kubernetes : Helm Package Manager with MySQL on GCP Kubernetes Engine

Docker & Kubernetes : Deploying Memcached on Kubernetes Engine

Docker & Kubernetes : EKS Control Plane (API server) Metrics with Prometheus

Docker & Kubernetes : Spinnaker on EKS with Halyard

Docker & Kubernetes : Continuous Delivery Pipelines with Spinnaker and Kubernetes Engine

Docker & Kubernetes: Multi-node Local Kubernetes cluster - Kubeadm-dind(docker-in-docker)

Docker & Kubernetes: Multi-node Local Kubernetes cluster - Kubeadm-kind(k8s-in-docker)

Docker & Kubernetes : nodeSelector, nodeAffinity, taints/tolerations, pod affinity and anti-affinity - Assigning Pods to Nodes

Docker & Kubernetes : Jenkins-X on EKS

Docker & Kubernetes : ArgoCD App of Apps with Heml on Kubernetes

Docker & Kubernetes : ArgoCD on Kubernetes cluster

Docker & Kubernetes : GitOps with ArgoCD for Continuous Delivery to Kubernetes clusters (minikube) - guestbook










Contact

BogoToBogo
contactus@bogotobogo.com

Follow Bogotobogo

About Us

contactus@bogotobogo.com

YouTubeMy YouTube channel
Pacific Ave, San Francisco, CA 94115

Pacific Ave, San Francisco, CA 94115

Copyright © 2024, bogotobogo
Design: Web Master