Deploy a Laravel project to K8s, simple and easy steps

Abdulrhman Almahaini
Insider Engineering
9 min readDec 19, 2022

Hello again my dear friends, after so many issues I had before on understanding deployment, how I can manage it, and how to deliver my application to a customer, and after so many days of searching on the internet trying to find the best and easy way to do that, I got the answers that I want to share with you today in this blog.

Quick introduction:

In this blog post, we will dive together into how to deploy our Laravel application to a K8s cluster. We are trying to explain step by step the process that we should do to achieve that simply and easily, hope you will enjoy it as much as I did, and thanks in advance for reading.

What is K8s?

Kubernetes, also known as K8s is a container orchestration tool that allows us to deploy, scale, and manage our Docker containers in an easy and programable way. It’s so famous nowadays and used by big companies worldwide like Google and many others.

K8s rely on multiple units to deploy a certain project, the smallest unit is called “Pods”, Pods are the place where our docker containers live, and K8s is responsible for duplicating those containers to multiple Pods to scale up the system. The issue with Pods is that whenever they are destroyed, their IP is changed. So, to resolve this, K8s introduced another unit called “Service”. Services in K8s are like gateways where they are sending the request directly to their corresponding Pod, and whenever a Pod is destroyed it will not matter anymore, Cause the attached Service will not be destroyed and requests will still be directed to the newly created Pods.

After this introduction, let’s jump into our main subject to deploy a Laravel project to K8s. You need to have:
1- Docker.
2- Kubectl: It’s a command line tool for K8s.
3- Minikube: A virtual machine that has our K8s Pods and Services.
4- Laravel project that we need to deploy.

The Application:

First, we will install a fresh Laravel project by using composer:

$ composer create-project laravel/laravel learn-dep-k8s
$ cd learn-dep-k8s

After we installed the application locally, we need to pack it inside a container, cause K8s does not care about the project itself but cares about containers.

For creating the container, we will use Docker, so we need to create a Dockerfile inside our application and add to it the service images that we need to have inside our container.

FROM composer:2.4.4 as build
WORKDIR /app
COPY . /app
RUN composer install
FROM php:8.0.24-apache
EXPOSE 80
COPY --from=build /app /app
COPY vhost.conf /etc/apache2/sites-available/000-default.conf
RUN chown -R www-data:www-data /app \
&& a2enmod rewrite

As you can notice in this Dockerfile we have two main sections. The first one is related to composer installation to have all the related dependencies, and the second one is related to the PHP web server preparations.

Running this Dockerfile will create a new Docker image containing all the files that belong to the container. To run the Dockerfile we should execute.

$ docker build -t learn-dep-k8s .

The -t learn-dep-k8s refers to the name of the container.

After this and for testing this Docker image, we need to run it.

$ docker run -ti -p 8080:80 learn-dep-k8s

The application now should be available if you visited http://localhost:8080/.

Publishing the Image:

In the previous step, we created a container image using Dockerfile but to be able to use this Docker image we should share it in a container registry. In this tutorial, I will use Docker hub since it’s easy to use.

To be able to push your image to the Docker hub you need to have an account in it, so if you don’t have an account yet please create one from this link https://hub.docker.com/signup.

After creating an account, you should link your Docker with your Docker hub account by executing.

$ docker login

The Docker hub accepts the images to be pushed if it has this format <username>/name-of-image. So, in order to change the name of the already created image you can execute the:

$ docker tag learn-dep-k8s <username>/learn-dep-k8s

Please replace <username> with the username that you created the Docker hub account with.

After changing the name of the image to the required format, now we can push it to the Docker hub so it can be available publicly to anyone who wants to use it.

$ docker push <my-username>/learn-dep-k8s

If you want to test this public image you have to remove the local image, we created earlier first then run the same command but with a small change.

$ docker run -it -p 8080:80 <username>/learn-dep-k8s

As you notice, you need to provide the name of the image with your username on the Docker hub. Now, if you visited http://localhost:8080/ you will be able to see the application as before.

K8s for Deployment

After making sure that our image is ready and can be used, we need to deploy it.

To interact with K8s we need to use Kubectl, the command line tool for K8s, we can deploy our image to a Pod using this command:

$ kubectl run learn-dep-k8s --image=<username>/learn-dep-k8s --port=80

Let’s break that command to know what each part is doing.

  • kubectl run learn-deps-k8s this part is the one responsible for creating the pod and it will create a pod with the name lear-deps-k8s.
  • --image=<usename>/learn-deps-k8s is the image we created earlier and pushed to the Docker hub.
  • --port=80 the port we exposed our image to use.

Running this command will take about a minute to create the container and push it to the pod we want. To check the operation and to make sure that our pod is ready or not you can execute:

$ kubectl get pods

This will show the pod we created and its status.

The pod is created and ready to be used

When creating a Pod, a dynamic IP is assigned to it. Whenever a Pod is deleted or crashed, K8s will create another Pod instead of it, and that new Pod will have a new IP address, this scenario will make it hard for you to access your Pod (your application) so K8s has another important unit to use in this case and it’s called “Service”.

Services as I said before are like gateways, they keep a static IP that you can use and access your Pod through, and they are not destroyed when the Pod attached to them is destroyed. They will stay alive and direct requests to your application.

To create a service, you can execute this command:

$ kubectl expose pods learn-dep-k8s --type=NodePort --port=80

You can check your service by executing:

$ kubectl get service

Its output can be like this:

The service is created

If you want to get the URL for accessing your Pod through this created service, you can execute the:

$ minikube service learn-dep-k8s

This will redirect you to the URL in your browser where you will be able to see your application running.

Deployment Files

Till now, we saw how to create our Docker image, and how to publish it to the Docker hub. We saw a way for deploying it to a K8s cluster. But the question now is, what if I want to extend my K8s cluster and add more configuration to it or what if I want to define environment variables that will be used inside the code that exists on the Pod, how can I achieve that using K8s?

K8s answers this question by presenting another unit called "deployment", the deployment files will have all the configurations that you want and the image you want to have inside your Pods, and how many Pods you want to have as replicas. Also, it will make sure that whenever a Pod is crashed or is deleted for any reason, a new one will be created automatically.

The deployment file should be named asdeployment.yml in your project, an example of that YAML file you can find below:

apiVersion: apps/v1
kind: Deployment
metadata:
name: learn-dep-k8s-deployment
namespace: default
labels:
app: learn-dep-k8s
spec:
replicas: 2
selector:
matchLabels:
app: learn-dep-k8s
template:
metadata:
labels:
app: learn-dep-k8s
spec:
containers:
- name: learn-dep-k8s
image: abdmahaini/learn-dep-k8s
ports:
- containerPort: 80
env:
- name: APP_KEY
value: base64:cUPmwHx4LXa4Z25HhzFiWCf7TlQmSqnt98pnuiHmzgY=
---apiVersion: v1
kind: Service
metadata:
name: learn-dep-k8s-service
namespace: default
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: learn-dep-k8s

As you can notice in this deployment file, we defined our Pod configuration such as image, replicas, and environment variables. Also, we defined our service configurations for the Service that will expose that Pod and be attached to it after running this deployment file.

Note: For linking the service to the pod you need to make sure that the selector property in the service matches the labels of the pod.

To run the deployment file, we need to execute the following command while we are in the project directory.

$ kubectl apply -f deployment.yml

This command will create the Pods we need (with all replicas) and the services we want, if you run minikube service learn-dep-k8s-service you will be redirected to the application page on the browser like before.

Let us try to delete a Pod and check what will happen. To get the running Pods you can execute kubectl get pod.

Running Pods Table

Get any of those Pods and execute kubectl delete pod <the podId you selected>, when you execute that and check the pods again you will notice that K8s created a new pod for your application, and this is because you created those Pods via using a deployment file.

Pods status after deletion

As you can notice I deleted the second pod and after checking again K8s created a new pod automatically for the application.

Ingress File

Till now everything is OK, we managed to deploy our application in K8s but still, something is missing, our deployed application is not accessible externally, so what should we do in that case, and what is K8s providing?

For making our application accessible for external users K8s has provided another unit that we can use, and it’s called “Ingress”.

Kubernetes Ingress is an API object that provides routing rules to manage external users’ access to the services in a Kubernetes cluster.

In the past, you might have used Nginx or Apache as a reverse proxy. The Ingress is the equivalent of a reverse proxy in Kubernetes. External requests come first to ingress and then directed to the service which will distribute it to the Pods.

Kubernetes Ingress Work Schema

Like what we did for deployment, in order to add ingress, we need to have an ingress.yml file that will contain our inbound rules.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: learn-dep-k8s-ingress
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: learn-dep-k8s-service
port:
number: 80

Before creating the ingress unit in our K8s cluster we need to enable the Nginx server that Minikube already has, so we need to execute:

$ minikube addons enable ingress

After enabling it you can execute:

$ kubectl create -f ingress.yml

This command will create Ingress and add it to your K8s cluster, to inspect that K8s ingress you can execute.

$ kubectl describe ing learn-dep-k8s-ingress

Finally, to visit the application you can visit http://localhost/ in your browser and your application will be served using K8s and Ingress.

Conclusion:

In this blog post, I tried to shed the light on the process of deploying a Laravel project to a K8s cluster using Docker and Docker hub for creating the container and registering it, to be able to use it later as a K8s pod container image. Hope you enjoyed this blog and find it helpful. See you all in another blog post and till then let me wish you luck and happy coding everyone.

--

--

Abdulrhman Almahaini
Insider Engineering

software developer in Insider, with 7+ experience in backend development.