Hosting Multiple Projects on a Single Server Instance
In a bid to save cost, I set out on hosting different projects on a single server, the process wasn't really all smooth. As always, I have written this article to save you time whenever you decide to implement something like this. Hosting projects on one server optimizes resources but also centralizes the control of different applications. Let's embark on a detailed exploration of how to effectively host multiple projects on a single server instance, using Digital Ocean's droplets as an example. This approach leverages Docker and Nginx, providing a robust environment for your web applications.
The Setup
Imagine a server directory structured as follows:
shared
app-one
app-two
Each folder serves its unique purpose, where shared
contains global configurations and app-one
and app-two
house individual project files.
The Shared Directory
Within the shared
directory, a docker-compose
file sets the stage for our Nginx service. This setup involves specifying the Nginx image, configuring port mappings, and defining volume mounts. This Nginx container is only used tp proxy pass to our various apps.
Here's a simplified version of the Docker-compose configuration:
Read also : Docker for NuxtJS Development and Deployment.
version: "3"
services:
nginx:
# This Nginx serves as only as a reverse proxy for the applications hosted on this server.
image: 'nginx:1.25-alpine'
ports:
- '80:80'
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d/:/etc/nginx/conf.d/
In the nginx.conf
file, a crucial line includes all configurations from the conf.d directory, allowing for separate domain configurations for each project.
include /etc/nginx/conf.d/*.conf;
Nginx Configuration
For each domain, a dedicated configuration file within nginx/conf.d/
directs traffic appropriately. A sample configuration might look like this:
server {
server_name api.foo.test api.bar.com;
#IP address below should be IPV4 address of the server running the application
#or IP address of the WSL IP gotten with "hostname -I" (172.25.42.230)
#and external port of the application
location / {
proxy_pass http://172.25.42.230:8000;
}
}
The IP address specified in the proxy_pass
, should be IPV4 address of the server on which the application is hosted and the external port of the application's Nginx. If you are using WSL for testing, you can get the IP address of your WSL, by opening an ubuntu terminal on WSL and run:
hostname -I
Modifying the etc/hosts
file is key to resolving the domain/domains name specify in the server_name above. The hosts files can be found at /etc/hosts
in Linux and /Windows/System32/drivers/etc/hosts
.You should add these in your hosts file to resolve the domains:
127.0.0.1 api.foo.test
127.0.0.1 api.bar.com
Application Setup
Within each application directory (e.g., app-one
), a docker-compose.yml
file describes the specific services, networks, and dependencies needed for that project. For PHP-FPM, ensure it runs on port 9000, aligning with the Nginx configuration:
Read also : Deploy Laravel App on DigitalOcean with Ploi.
version: "3"
services:
mysql:
image: 'mysql/mysql-server:8.0'
ports:
- '${DB_PORT}:${DB_PORT}'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: '%'
MYSQL_TCP_PORT: '${DB_PORT}'
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 0
volumes:
#APP_NAME is used to avoid conflicts with other projects
- "~/.docker-volumes/${COMPOSE_PROJECT_NAME}/mysql:/var/lib/mysql"
- "./docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh"
- "./docker/mysql/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf"
networks:
- backend
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}" ]
retries: 3
timeout: 5s
php:
build:
context: ./docker/php
args:
APP_ENV: '${APP_ENV}'
volumes:
- "./:/var/www/html"
# Do not add any configuration file here, it will crash app
networks:
- backend
depends_on:
- mysql
nginx:
image: 'nginx:1.25-alpine'
ports:
- '8000:80'
volumes:
- ./:/var/www/html
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/conf.d/:/etc/nginx/conf.d/
networks:
- backend
depends_on:
- php
phpmyadmin:
build: ./docker/phpmyadmin
ports:
- '${PMA_EXTERNAL_PORT}:80'
environment:
#https://docs.phpmyadmin.net/en/release_5_1_4/setup.html#docker-environment-variables
#https://docs.phpmyadmin.net/en/release_5_1_4/setup.html#customizing-configuration-file-using-docker-compose
PMA_ARBITRARY: 0 #Don't ask for server name
PMA_HOST: '${DB_CONNECTION}'
PMA_PORT: '${DB_PORT}'
MYSQL_USER: '${DB_USERNAME}' #Don't use 'root' as user here, it will cause errors
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MAX_EXECUTION_TIME: 600
MEMORY_LIMIT: '256M'
UPLOAD_LIMIT: '2G'
volumes:
- "./docker/phpmyadmin/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php"
depends_on:
- mysql
networks:
- backend
cron:
build:
context: ./docker/cron
volumes:
- "./:/var/www/html"
# Adding crontabs volume here will crash cron. I don't know why yet
networks:
- backend
depends_on:
- mysql
networks:
backend:
driver: bridge
volumes:
mysql:
driver: local
Conclusion
By following this guide, developers can streamline their workflow, reduce resource redundancy, and enhance the scalability of their server environments. Whether you're managing two projects or twenty, this setup paves the way for a more organized and efficient development lifecycle.