summaryrefslogtreecommitdiff
path: root/doc/ci/examples/deployment/composer-npm-deploy.md
blob: 62607320410a9a1411dc9af2ff098a07030377b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
---
stage: Release
group: Release
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: tutorial
---

# Running Composer and npm scripts with deployment via SCP in GitLab CI/CD

This guide covers the building of dependencies of a PHP project while compiling assets via an npm script using [GitLab CI/CD](../../README.md).

While it is possible to create your own image with custom PHP and Node.js versions, for brevity we use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and Node.js installed.

```yaml
image: tetraweb/php
```

The next step is to install zip/unzip packages and make composer available. We place these in the `before_script` section:

```yaml
before_script:
  - apt-get update
  - apt-get install zip unzip
  - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
  - php composer-setup.php
  - php -r "unlink('composer-setup.php');"
```

This makes sure we have all requirements ready. Next, run `composer install` to fetch all PHP dependencies and `npm install` to load Node.js packages. Then run the `npm` script. We need to append them into `before_script` section:

```yaml
before_script:
  # ...
  - php composer.phar install
  - npm install
  - npm run deploy
```

In this particular case, the `npm deploy` script is a Gulp script that does the following:

1. Compile CSS & JS
1. Create sprites
1. Copy various assets (images, fonts) around
1. Replace some strings

All these operations put all files into a `build` folder, which is ready to be deployed to a live server.

## How to transfer files to a live server

You have multiple options: rsync, SCP, SFTP, and so on. For now, use SCP.

To make this work, you must add a GitLab CI/CD Variable (accessible on `gitlab.example/your-project-name/variables`). Name this variable `STAGING_PRIVATE_KEY` and set it to the **private** SSH key of your server.

### Security tip

Create a user that has access **only** to the folder that needs to be updated.

After you create that variable, make sure that key is added to the Docker container on run:

```yaml
before_script:
  # - ....
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - mkdir -p ~/.ssh
  - eval $(ssh-agent -s)
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
```

In order, this means that:

1. We check if the `ssh-agent` is available and we install it if it's not.
1. We create the `~/.ssh` folder.
1. We make sure we're running bash.
1. We disable host checking (we don't ask for user accept when we first connect to a server, and since every job equals a first connect, we need this).

And this is basically all you need in the `before_script` section.

## How to deploy

As we stated above, we need to deploy the `build` folder from the Docker image to our server. To do so, we create a new job:

```yaml
stage_deploy:
  artifacts:
    paths:
      - build/
  only:
    - dev
  script:
    - ssh-add <(echo "$STAGING_PRIVATE_KEY")
    - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp"
    - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp
    - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live"
    - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old"
```

Here's the breakdown:

1. `only:dev` means that this build runs only when something is pushed to the `dev` branch. You can remove this block completely and have everything run on every push (but probably this is something you don't want).
1. `ssh-add ...` we add that private key you added on the web UI to the Docker container.
1. We connect via `ssh` and create a new `_tmp` folder.
1. We connect via `scp` and upload the `build` folder (which was generated by a `npm` script) to our previously created `_tmp` folder.
1. We connect again via `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`.
1. We connect to SSH and remove the `_old` folder.

What's the deal with the artifacts? We tell GitLab CI/CD to keep the `build` directory (later on, you can download that as needed).

### Why we do it this way

If you're using this only for stage server, you could do this in two steps:

```yaml
- ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/live/*"
- scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/live
```

The problem is that there's a small period of time when you don't have the app on your server.

Therefore, for a production environment we use additional steps to ensure that at any given time, a functional app is in place.

## Where to go next

Since this was a WordPress project, I gave real life code snippets. Some further ideas you can pursue:

- Having a slightly different script for the default branch allows you to deploy to a production server from that branch and to a stage server from any other branches.
- Instead of pushing it live, you can push it to WordPress official repository (with creating a SVN commit, etc.).
- You could generate i18n text domains on the fly.

---

Our final `.gitlab-ci.yml` looks like this:

```yaml
image: tetraweb/php

before_script:
  - apt-get update
  - apt-get install zip unzip
  - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
  - php composer-setup.php
  - php -r "unlink('composer-setup.php');"
  - php composer.phar install
  - npm install
  - npm run deploy
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - mkdir -p ~/.ssh
  - eval $(ssh-agent -s)
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

stage_deploy:
  artifacts:
    paths:
      - build/
  only:
    - dev
  script:
    - ssh-add <(echo "$STAGING_PRIVATE_KEY")
    - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp"
    - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp
    - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live"
    - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old"
```