SPA deployment with GitLab
A small example of a single-page application deployment using GitLab, docker and nginx
In the last projects, I started using GitLab, not only as git repository server, but also as DevOps platform. So here I am going to describe a very simple architecture to deploy a single page application using Docker and docker-compose, nginx. In this specific project I used also dotnet-core for the back-end API and VueJS as front-end framework, but it is language-agnostic, meaning that you can replace whatever back-end or front-end you prefer.
First of all, following the separation of concerns, let’s consider each part on its own. I have created 3 repositories:
- Front-end
- Back-end
- Deploy
I have considered using one single repo to maintain all the codebase, but I ended-up with this structure for the following reasons:
- Often the teams working on back-end and front-end are not the same, so in this way they are completely independent from each-other
- In that way the project structure can be re-used with all frameworks/languages combinations
- We are almost separating the local development phase, with the DevOps phase (apart from the inclusion of the
gitlab-ci.yml
files).
Front-end static files generation
I have used the VueJS and the related vue-cli tools to generate the static files, but it doesn’t really matter in the whole application; the only important thing is to generate static files.
In our case the gitlab-ci.yml
file will look like:
build site:
image: node:latest
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist
description =
- It creates a job named
build site
- It executes the script in a docker image
node:latest
- It creates a
build
stage to execute the following script in the image aforementioned:
npm ci
npm run build
- It stores the artifacts created in the
dist
directory.
Back-end API build
Since that we make use of dotnet-core, we need to build the project, so this step could be unnecessary if you choose some language that doesn’t require to be compiled (e.g. NodeJs, Python, etc…).
Our gitlab-ci.yml
:
build:
image: microsoft/dotnet:sdk
stage: build
script:
- dotnet restore
- dotnet publish -c Release -o out
artifacts:
paths:
- out
As you can see, the file looks very similar to the previous one.
- It creates a
build
job - It uses a different docker image to build the project
microsoft/dotnet:sdk
- It creates a
build
stage to execute the following scripts:
- dotnet restore # it restores the dependencies
- dotnet publish -c Release -o out # it builds the project
- It stores the artifacts created in the
out
directory.
So at the moment, we have 2 projects that produces their own artifacts. It’s time to let them talk!
Deploy project
Project structure
|__ api
| |__ Dockerfile
|__ nginx
| |__ nginx.conf
|__ gitlab-ci.yml
|__ docker-compose.yml
This project performs the following steps:
- Retrieve files generated from the front-end
- Retrieve files generated from the back-end
- Set-up nginx
docker-compose.yml:
version: '3'
services:
nginx:
image: nginx:latest
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./dist/:/var/www/html/
ports:
- 80:80
- 443:443
networks:
- proxy-net
web:
build: ./api/
networks:
- proxy-net
networks:
proxy-net:
From the docker-compose file it is important to notice two folders:
./dist/
used to map the static file folders the nginx container./api/
the folder containing theDockerfile
used to setup the dotnet APIs container.
For completeness here the content of the Dockerfile
used to run the dotnet APIs.
# Build runtime image
FROM microsoft/dotnet:aspnetcore-runtime
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "Api.dll"]
Where Api is the name of the dotnet API project.
nginx.conf:
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
server_name <name_of_the_server>; # TODO: to be replaced
location / {
# This would be the directory where your SPA static files are stored at
root /var/www/html/;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://web;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
}
}
}
The previous nginx configuration is to be intended as example only. For nginx production configuration please refer to the following resources
We are assuming this project is deployed by an agent running on a machine with docker-compose installed.
Let’s have a look at gitlab-ci.yml
.
deploy:
stage: deploy
only:
- "master"
environment: production
script:
- "curl -L --header \"PRIVATE-TOKEN: $TOKEN\" \"https://gitlab.com/api/v4/projects/14072554/jobs/artifacts/master/download?job=build+site\" --output artifacts.zip"
- unzip artifacts.zip
- "curl -L --header \"PRIVATE-TOKEN: $TOKEN\" \"https://gitlab.com/api/v4/projects/10080203/jobs/artifacts/master/download?job=build\" --output api.zip"
- unzip api.zip
- cp -R out/* api/
- sh down.sh
- sh up.sh
- It creates a
deploy
job - It creates a
deploy
stage with the following script section:
- "curl -L --header \"PRIVATE-TOKEN: $TOKEN\" \"https://gitlab.com/api/v4/projects/14072554/jobs/artifacts/master/download?job=build+site\" --output artifacts.zip"
- unzip artifacts.zip
- "curl -L --header \"PRIVATE-TOKEN: $TOKEN\" \"https://gitlab.com/api/v4/projects/10080203/jobs/artifacts/master/download?job=build\" --output api.zip"
- unzip api.zip
- cp -R out/* api/
- docker-compose up --build -d
So let’s dive deeper in the script commands:
- Retrieve the front-end artifacts by using the gitlab API (for non-enterprise users)
- It also requires to set the
PRIVATE-TOKEN
variable with a personal access token, that can be generated fromUser Settings -> Access Tokens
- Unzip the front-end artifacts
- Retrieve the back-end artifacts
- Unzip the back-end artifacts
- Run
docker-compose
Conclusion
I have tried to describe only the steps necessary to deploy a single-page application using docker and GitLab, without considering the initial steps of repositories creation and GitLab runner setup.
While the setup of the GitLab runner used to deploy the application is required, the front-end and back-end builds make use of the shared runners available in GitLab.
For further details, please don’t hesitate to get in touch!