
We check three major step in this guide
- Setup laravel project with docker compose
- Deploy the stack to the swarm
- Create gitlab-ci
Setup laravel project with docker compose
we will explore the process of deploying a laravel project using docker swarm and setting up a CI/CD pipline to automate the deployment process.
Now let’s start with containerize a laravel project with docker compose
we need three separate service containers:
- Anapp service running PHP7.4-FPM;
- Adb service running MySQL 5.7;
- Annginx service that uses theapp service to parse PHP code
Step 1. Set a env variable in project
In root directory of project we have .env file now we need to update some variable
DB_CONNECTION=mysqlDB_HOST=dbDB_PORT=3306DB_DATABASE=experienceDB_USERNAME=experience_userDB_PASSWORD=your-password
Step 2. Setting up the application’s Docekrfile
we need to build a custom image for the application container. We’ll create a new Dockerfile for that.
Docker file
FROM php:7.4-fpm# Install system dependenciesRUN apt-get update && apt-get install -y \git \curl \libpng-dev \libonig-dev \libxml2-dev \zip \unzip# Clear cacheRUN apt-get clean && rm -rf /var/lib/apt/lists/*# Install PHP extensionsRUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd# Get latest ComposerCOPY --from=composer:latest /usr/bin/composer /usr/bin/composer# Set working directoryWORKDIR /var/www
Step 3. Setting up Nginx config and Database dump file
In root directory create a new directory called docker-compose
Now we need two other directories, a nginx directory and mysql directory
So we have this two route in our project
- laravel-project/docker-compose/nginx/
- laravel-project/docker-compose/mysql/
In nginx directory create a file called experience.conf we write nginx config in this file like:
server { listen 80; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/public; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; }}
In mysql directory create a file called init_db.init we write mysql initialization in this file like:
DROP TABLE IF EXISTS `places`;CREATE TABLE `places` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `visited` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;INSERT INTO `places` (name, visited) VALUES ('Berlin',0),('Budapest',0),('Cincinnati',1),('Denver',0),('Helsinki',0),('Lisbon',0),('Moscow',1);
Step 4. Creating a multi container with docker-compose
We need a building three container that should share networks and data volumes.
Ok so create a docker-compose file in root directory of project
For craete a network for connecting services we define network in docker-compose file like this:
networks: experience: driver: bridge
App service:
app: build: context: ./ dockerfile: Dockerfile image: travellist container_name: experience-app restart: unless-stopped working_dir: /var/www/ volumes: - ./:/var/www networks: - experience
DB service:
db: image: mysql:8.0 container_name: experience-db restart: unless-stopped environment: MYSQL_DATABASE: ${DB_DATABASE} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} SERVICE_TAGS: dev SERVICE_NAME: mysql volumes: - ./docker-compose/mysql:/docker-entrypoint-initdb.d networks: - experience
Nginx service:
nginx: image: nginx:1.17-alpine container_name: experience-nginx restart: unless-stopped ports: - 8000:80 volumes: - ./:/var/www - ./docker-compose/nginx:/etc/nginx/conf.d networks: - experience
So our docker-compose file be like this:
version: "3.7"services: app: build: context: ./ dockerfile: Dockerfile image: travellist container_name: experience-app restart: unless-stopped working_dir: /var/www/ volumes: - ./:/var/www networks: - experience db: image: mysql:8.0 container_name: experience-db restart: unless-stopped environment: MYSQL_DATABASE: ${DB_DATABASE} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} SERVICE_TAGS: dev SERVICE_NAME: mysql volumes: - ./docker-compose/mysql:/docker-entrypoint-initdb.d networks: - experience nginx: image: nginx:alpine container_name: experience-nginx restart: unless-stopped ports: - 8100:80 volumes: - ./:/var/www - ./docker-compose/nginx:/etc/nginx/conf.d/ networks: - experiencenetworks: experience: driver: bridge
Step 5. Running application with docker compose
Now we can build the app image with this command:$ docker-compose build app
When the build is finished, we can run the environment in background mode with:$ docker-compose up -d
Output:Creating exprience-db ... doneCreating exprience-app ... doneCreating exprience-nginx ... done
to show information about the state of your active services, run:$ docker-compose ps
Well in these 5 simple steps, we have successfully ran our application.
Now we have a docker-compose file for our application that needs for using in docker swarm.
Let’s start Initialize docker swarm.
After installing docker in your server
*attention: To install Docker, be sure to use the official documentationinstall docker
check docker information with this command:$ docker info
You should see “swarm : inactive” in output
For activate swarm in docker use this command:$ docker swarm init
The docker engine targeted by this command becomes a manager in the newly created single-node swarm.
What we want to use is the services of this docker swarm.
We want to update our service like app with docker swarm, The advantage of updating our service in Docker Swarm is that there is no need to down the app service first, update the service, and then bring the service up.
In this method, with one command, we can give the image related to the service to Docker and give the update command. Docker raises the new service without down the old service and slowly transfers the load from the old service to the new service.
When running Docker Engine in swarm mode, we can use docker stack deploy to deploy a complete application stack to the swarm. The deploy command accepts a stack description in the form of a Compose file.
So we down our docker compose with this command:$ docker-compose down
And create our stack.
ok if everything is ok until now take a rest
Deploy the stack to the swarm
$ docker stack deploy --compose-file docker-compose.yml <your-stack-name>
For example :$ docker stack deploy --compose-file docker-compose.yml staging
Probably you see this in output:
Creating network staging_exprienceCreating service staging_nginxfailed to create service staging_nginx: Error response from daemon: The network staging_exprience cannot be used with services. Only networks scoped to the swarm can be used, such as those created with the overlay driver.
This is because of “driver: bridge” for deploying your service in swarm mode you must use overlay driver for network if you remove this line in your docker compose file When the stack is being deployed this network will be create on overlay driver automatically. So our docker-compose file in network section be like this:
networks: experience:
And run upper command:$ docker stack deploy --compose-file docker-compose.yml staging
For now you probably you see this error :
failed to create service staging_nginx: Error response from daemon: The network staging_experience cannot be used with services. Only networks scoped to the swarm can be used, such as those created with the overlay driver.
Get network list in your docker:$ docker network ls
Output:NETWORK ID NAME DRIVER SCOPE30f94ae1c94d staging_experience bridge local
So your network has local scope yet because in first time deploy stack this network save in local scope and we must remove that by:$ docker network rm staging_experience
After all this run command:$ docker stack deploy --compose-file docker-compose.yml staging
Output:Creating network staging_experienceCreating service staging_appCreating service staging_dbCreating service staging_nginx
Now get check stack by:$ docker stack ls
Output:NAME SERVICESstaging 3
And get service list by:$ docker service ls
Output:
If your REPLICAS is 0/1 something wrong is your service
For checking service status run this command:$ docker service ps staging_app
for example
And for check detail of service run this command:$ docker service logs staging_app
for example
Output of this command show you what is problem of your service.
And for updating your a service with an image the command you need is this:
$ docker service update --image "<your-image>" "<name-of-your-service>" --force
That's it your docker swarm is ready for zero down time deployment :)))
Last step for have a complete process zero down time deployment is create pipeline in gitlab.
Create gitlab-ci
In this step we want create a pipeline in gitlab for build, test and deploy a project
So we have three stage:
stages: - Build - Test - Deploy
Ok let’s clear what we need and what is going on in this step .
We want update laravel project and push our change in gitlab create a new image of this changes and test that and after that log in to host server pull that updated image in server, and update service of project.
For login to server we need define some variable in gitlab in your repository goto setting->CI/CD->VARIABLES Add variable
Add this variables:
CI_REGISTRY :https://registry.gitlab.com
DOCKER_AUTH_CONFIG:
{ "auths": { "registry.gitlab.com": { "auth": "<auth-key>" } }}
auth-key is base64 hash of “gitlab-username:gitlab-password”
SSH_KNOWN_HOSTS:
Like 192.168.1.1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCGUCqCK3hNl+4TIbh3+Af3np+v91AyW4+BxXRtHBC2Y/uPJXF2jdR6IHlSS/0RFR3hOY+8+5a/r8O1O9qTPgxG8BSIm9omb8YxF2c4Sz/USPDK3ld2oQxbBg5qdhRN28EvRbtN66W3vgYIRlYlpNyJA+b3HQ/uJ+t3UxP1VjAsKbrBRFBth845RskSr1V7IirMiOh7oKGdEfXwlOENxOI7cDytxVR7h3/bVdJdxmjFqagrJqBuYm30
You can see how generate ssh key in this post:generate sshkey
SSH_PRIVATE_KEY:
SSH_REMOTE_HOST: root@
This is your variables in gitlab.
So let’s back to gitlab-ci
In root directory of project create a new file .gitlab-ci.yml and
set build stage
set test stage
And in the last set deploy stage like:
stages: - Build - Test - Deployvariables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHAbuild: stage: Build image: docker:20.10.16 services: - docker:dind script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build --pull -f Dockerfile -t $IMAGE_TAG . - docker push $IMAGE_TAGpreparation: stage: Test image: $IMAGE_TAG needs: - build script: - composer install artifacts: expire_in: 1 day paths: - ./vendor cache: key: ${CI_COMMIT_REF_SLUG}-composer paths: - ./vendorunit-test: stage: Test image: $IMAGE_TAG services: - name: mysql:8 alias: mysql-test needs: - preparation variables: APP_KEY: ${APP_KEY} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} DB_HOST: ${DB_HOST} DB_USERNAME: ${DB_USERNAME} DB_PASSWORD: ${DB_PASSWORD} script: - php vendor/bin/phpunitstaging-deploy: stage: Deploy extends: - .deploy-script variables: APP: "stackdemo_app" STACK: "travellist-staging" only: - develop needs: - unit-test environment: name: stage.remote-docker: variables: DOCKER_HOST: ssh://${SSH_REMOTE_HOST} image: docker:20.10.16 before_script: - eval $(ssh-agent -s) - echo $IMAGE_TAG - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - echo "HOST *" > ~/.ssh/config - echo "StrictHostKeyChecking no" >> ~/.ssh/config - echo -n $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY.deploy-script: extends: - .remote-docker script: - cp $develop_config /root/project/core - docker pull $IMAGE_TAG - docker service update --image "$IMAGE_TAG" "$APP" --force dependencies: []
Change something in your project and push to gitlab and wait for it
To see all pipeline pass like this :
And this is beautiful.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse