<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>mcodex</title>
	<atom:link href="https://mcodex.ru/feed/" rel="self" type="application/rss+xml" />
	<link>https://mcodex.ru/</link>
	<description>Короткие статьи об основных понятиях, терминах и концепциях веб-разработки</description>
	<lastBuildDate>Fri, 22 May 2026 12:19:35 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://mcodex.ru/wp-content/uploads/2025/11/cropped-favicon_650-32x32.png</url>
	<title>mcodex</title>
	<link>https://mcodex.ru/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Полезные команды Git</title>
		<link>https://mcodex.ru/git-commands/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Thu, 21 May 2026 08:44:03 +0000</pubDate>
				<category><![CDATA[Git]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=298</guid>

					<description><![CDATA[<p>Шапргалка по командам Git, используемых в ежедневной работе.</p>
<p>Сообщение <a href="https://mcodex.ru/git-commands/">Полезные команды Git</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<ul class="wp-block-list">
<li><a href="#git-1" type="internal" id="#git-1">Не отслеживать изменение прав на файлы  и папки</a></li>



<li><a href="#git-2" type="internal" id="#git-2">Исправить описание в последнем коммите</a></li>



<li><a href="#git-3">Анализ проекта</a></li>



<li><a href="#git-4" type="internal" id="#git-4">Изменить удаленный репозиторий</a></li>
</ul>



<p class="wp-block-paragraph" id="git-1"><strong>Не отслеживать изменение прав</strong> </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>git config core.fileMode false</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">core.fileMode</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-2"><strong>Исправить описание в последнем коммите</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>git commit --amend -m "New commit message"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">commit</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--amend</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-m</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">New commit message</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-3"><strong>Анализ проекта</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># Какие файлы меняются чаще всего
git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20

# Все контрибьюторы отсортированные по числу коммитов
git shortlog -sn --no-merges

# Кто активен за последние полгода
git shortlog -sn --no-merges --since="6 months ago"

# Количество коммитов помесячно
git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c

#Где скапливаются баги, отфильтровано по коммитам с ключевыми словами
git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20

# Как часто делаются revert/hotfix
git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># Какие файлы меняются чаще всего</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=format:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--name-only</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1 year ago</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-nr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">head</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-20</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Все контрибьюторы отсортированные по числу коммитов</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">shortlog</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sn</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Кто активен за последние полгода</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">shortlog</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sn</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--no-merges</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">6 months ago</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Количество коммитов помесячно</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%ad</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--date=format:</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%Y-%m</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">#Где скапливаются баги, отфильтровано по коммитам с ключевыми словами</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-E</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--grep=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">fix|bug|broken</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--name-only</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--format=</span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">uniq</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sort</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-nr</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">head</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-20</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Как часто делаются revert/hotfix</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">log</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--oneline</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--since=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1 year ago</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-iE</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">revert|hotfix|emergency|rollback</span><span style="color: #ECEFF4">&#39;</span></span></code></pre></div>



<p class="wp-block-paragraph" id="git-4"><strong>Изменить удаленный репозиторий</strong><br>Проект склонирован через https, нужно изменить на  ssh</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># текущее состояние
git remote -v
#origin https://gitlab.com/my-username/my-project.git (fetch)
#origin https://gitlab.com/my-username/my-project.git (push)


# замена
git remote set-url origin git@gitlab.com:my-username/my-project.git

# проверка
git remote -v
# origin git@gitlab.com:my-username/my-project.git (fetch)
# origin git@gitlab.com:my-username/my-project.git (push)
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># текущее состояние</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span>
<span class="line"><span style="color: #616E88">#origin https://gitlab.com/my-username/my-project.git (fetch)</span></span>
<span class="line"><span style="color: #616E88">#origin https://gitlab.com/my-username/my-project.git (push)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># замена</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">set-url</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">origin</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">git@gitlab.com:my-username/my-project.git</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># проверка</span></span>
<span class="line"><span style="color: #88C0D0">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">remote</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span>
<span class="line"><span style="color: #616E88"># origin git@gitlab.com:my-username/my-project.git (fetch)</span></span>
<span class="line"><span style="color: #616E88"># origin git@gitlab.com:my-username/my-project.git (push)</span></span>
<span class="line"></span></code></pre></div>
<p>Сообщение <a href="https://mcodex.ru/git-commands/">Полезные команды Git</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Использование MySQL в контейнере Docker</title>
		<link>https://mcodex.ru/mysql-in-docker/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sun, 17 May 2026 10:43:47 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=220</guid>

					<description><![CDATA[<p>Пошаговая инструкция по разворачиванию СУБД MySQL в docker-контейнере и подключении к нему проектов.</p>
<p>Сообщение <a href="https://mcodex.ru/mysql-in-docker/">Использование MySQL в контейнере Docker</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">1. Установка Docker</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>curl -fsSL https://get.docker.com | sh

# добавляем текущего пользователя в группу docker
sudo usermod -aG docker $USER
newgrp docker</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">curl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-fsSL</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">https://get.docker.com</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sh</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># добавляем текущего пользователя в группу docker</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">usermod</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-aG</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$USER</span></span>
<span class="line"><span style="color: #88C0D0">newgrp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span></span></code></pre></div>



<h2 class="wp-block-heading">2. Файл docker-compose.yaml</h2>



<p class="wp-block-paragraph">Создаем папку, например, /docker-containers/mysql, в ней файл docker-compose.yaml</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>services:
  mysql:
    image: mysql:8.0
    container_name: mysql_container
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: your_root_password
      MYSQL_DATABASE: my_project_db
      MYSQL_USER: my_project_user
      MYSQL_PASSWORD: my_project_password
    volumes:
      - mysql_data:/var/lib/mysql
      - ./conf.d:/etc/mysql/conf.d
    ports:
      - "127.0.0.1:3306:3306"

volumes:
  mysql_data:</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">services</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql:8.0</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">container_name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">restart</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">always</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">environment</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_ROOT_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">your_root_password</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_DATABASE</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_USER</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">MYSQL_PASSWORD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_password</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_data:/var/lib/mysql</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">./conf.d:/etc/mysql/conf.d</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">ports</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">127.0.0.1:3306:3306</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">volumes</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">mysql_data</span><span style="color: #ECEFF4">:</span></span></code></pre></div>



<p class="wp-block-paragraph">Используем образ MySQL фиксированной версии, что бы в случае пересоздания контейнера не столкнуться с проблемами несовместимости, которые потенциально могут быть, если использовать образ latest.</p>



<p class="wp-block-paragraph">Пароли и имена придумываем свои, пароли долджны быть сложные,  MYSQL_ROOT_PASSWORD используется при инициализации контейнера, в дальнейшем он будет нужен для подключения к MySQL от имени пользователя root, остальные данные будут нужны при подключении проекта.</p>



<h2 class="wp-block-heading">3. Запускаем контейнер</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cd /docker-containers/mysql
docker compose up -d

# Проверяем, что контейнер запущен
docker ps
docker logs mysql_container

# включаем автозапуск docker после перезагрузки VPS
sudo systemctl enable docker</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/docker-containers/mysql</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">compose</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">up</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-d</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Проверяем, что контейнер запущен</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ps</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">logs</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># включаем автозапуск docker после перезагрузки VPS</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">enable</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker</span></span></code></pre></div>



<p class="wp-block-paragraph">При первом запуске контейнера Docker-образ MySQL сам создаёт базу и пользователя, а так-же дает пользователю все права на эту базу.</p>



<h2 class="wp-block-heading">4. Импорт дампа в контейнер</h2>



<p class="wp-block-paragraph">На данном шаге в нашем контейнере существует пустая база данных <strong>my_project_db</strong>, в которую можно, при необходимости, загружить дамп проекта. Для этого выполняем mysql внутри контейнера.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -i mysql_container mysql -u my_project_user -p'my_project_password' my_project_db &lt; /path/to/dump/backup-dump.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/path/to/dump/backup-dump.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Важно:</strong> это работает только при <strong>первом</strong> запуске, когда volume ещё пустой. Если контейнер уже запускался и данные есть — переменные игнорируются, база уже существует.</p>



<p class="wp-block-paragraph">Проверям, что импорт прошел успешно.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -it mysql_container mysql -u my_project_user -p'my_project_password' my_project_db -e "SHOW TABLES;"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-it</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-e</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SHOW TABLES;</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">В выводе должен быть список таблиц в базе данных  <strong>my_project_db</strong></p>



<h2 class="wp-block-heading">5. Подключаем проект</h2>



<p class="wp-block-paragraph">Теперь нужно подключить проект к контейнеру с базой данных. Проекты могут использовать разные фреймворки и CMS.</p>



<p class="wp-block-paragraph"><strong>Вариант А. Проект на WordPress. </strong></p>



<p class="wp-block-paragraph">Переходим в папку проекта, открываем файл wp-config.php, меняем значения констант.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>define( 'DB_NAME', 'my_project_db' );
define( 'DB_USER', 'my_project_user' );
define( 'DB_PASSWORD', 'my_project_password' );
define( 'DB_HOST', '127.0.0.1:3306' );</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_NAME</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_db</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_USER</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_PASSWORD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">define(</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DB_HOST</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">127.0.0.1:3306</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">Проверяем, что сайт открывается, нет ошибки подключения в базе данных. <br>После подключение проекта к контейнеру, СУБД на хостинге больше не нужна, можно удалить.</p>



<h2 class="wp-block-heading">6. Добавление еще одной базы данных</h2>



<p class="wp-block-paragraph">Сейчас в контейнере создана только одна база данных для проекта my_project. Предположим, на этом же VPS расположен еще один проект, my_project2. Создадим в этом же контейнере еще одну БД для этого проекта. Для этого заходим в контейнер как пользователь root (пароль берем из MYSQL_ROOT_PASSWORD файла docker-compose.yaml ) и создаем нового пользователя и базу данных.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly># Зайди в контейнер
docker exec -it mysql_container mysql -u root -p

# Внутри MySQL создай всё для второго сайта
CREATE DATABASE my_project2_db;
CREATE USER 'my_project2_user'@'%' IDENTIFIED BY 'my_project2_password';
GRANT ALL PRIVILEGES ON my_project2_db.* TO 'my_project2_user'@'%';
QUIT;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88"># Зайди в контейнер</span></span>
<span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-it</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Внутри MySQL создай всё для второго сайта</span></span>
<span class="line"><span style="color: #88C0D0">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">DATABASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project2_db</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">USER</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">IDENTIFIED</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">GRANT</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ALL</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">PRIVILEGES</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ON</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project2_db.</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">TO</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project2_user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">QUIT</span><span style="color: #81A1C1">;</span></span></code></pre></div>



<p class="wp-block-paragraph">Подключение во второй проект аналогично п5.</p>



<h2 class="wp-block-heading">7. Хранение данных</h2>



<p class="wp-block-paragraph">Разбираемся, где лежат файлы MySQL на хост-машине. <br>В docker-compose.yaml указан именованный том <strong>mysql_data</strong>.  По сути это папка на хосте, в которой лежат файлы MySQL. Так же указано, что на нее маппируется папка /var/lib/mysql в контейнере (стандартная папка с данными MySQL).  Что бы узнать точное имя тома (папки) можно выполнить команду: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker volume ls

# Вывод
# DRIVER    VOLUME NAME
# local     mysql-docker_mysql_data
# local     mysql_mysql_data
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">volume</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ls</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Вывод</span></span>
<span class="line"><span style="color: #616E88"># DRIVER    VOLUME NAME</span></span>
<span class="line"><span style="color: #616E88"># local     mysql-docker_mysql_data</span></span>
<span class="line"><span style="color: #616E88"># local     mysql_mysql_data</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Видно, что точное имя тома <strong>mysql_mysql_data</strong>, так как к имени, указанному в опции volume добавляется имя папки, в которой лежит docker-compose.yaml, которое считается префиксом проекта.</p>



<p class="wp-block-paragraph">Проверяем, где находится данный том</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker volume inspect mysql_mysql_data

# Вывод
# ....
# "Mountpoint": "/var/lib/docker/volumes/mysql_mysql_data/_data",
# "Name": "mysql_mysql_data",
# ....
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">volume</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">inspect</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_mysql_data</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># Вывод</span></span>
<span class="line"><span style="color: #616E88"># ....</span></span>
<span class="line"><span style="color: #616E88"># &quot;Mountpoint&quot;: &quot;/var/lib/docker/volumes/mysql_mysql_data/_data&quot;,</span></span>
<span class="line"><span style="color: #616E88"># &quot;Name&quot;: &quot;mysql_mysql_data&quot;,</span></span>
<span class="line"><span style="color: #616E88"># ....</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Mountpoint &#8212; это и есть путь к папке тома на хост-машине. <br>Том не зависит от контейнера, поэтому при остановке, перезапуске, удалении контейнера данные в базе данных будут сохранены.</p>



<p class="wp-block-paragraph"><strong>Сделать дамп одной базы данных</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec mysql_container mysqldump \
  -u my_project_user -p'my_project_password' \
  my_project_db > my_project_db.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysqldump</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_db.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Сделать дамп всех баз данных</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec mysql_container mysqldump \
  -u root -p'your_root_password' \
  --all-databases > backup_all.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysqldump</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">root</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">your_root_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">--all-databases</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">backup_all.sql</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Восстановление из дампа</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker exec -i mysql_container mysql \
  -u my_project_user -p'my_project_password' \
  my_project_db &lt; backup.sql</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">docker</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-i</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql_container</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mysql</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">my_project_user</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my_project_password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #A3BE8C">my_project_db</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">backup.sql</span></span></code></pre></div>



<h2 class="wp-block-heading">8. Полезные команды docker</h2>



<p class="wp-block-paragraph">Напоминание: <br>1.  mysql_container &#8212; это не зарезервированное слово, это имя контейнера, заданное в файле docker-compose.yaml (см. п2), может быть любым.<br>2. команда &#8216;docker&#8217; может выполнятся из любой папки. <br>3. команда &#8216;docker compose&#8217; выполняется из папки, в которой находится файл docker-compose.yaml (или указывается путь к файлу через флаг -f)</p>



<figure class="wp-block-table is-style-stripes has-small-font-size"><table class="has-fixed-layout"><tbody><tr><td><strong>Действие</strong></td><td><strong>Команда</strong></td></tr><tr><td>Запустить контейнер в режиме демона</td><td>docker compose up -d</td></tr><tr><td>Посмотреть все запущенные контейнеры</td><td>docker ps</td></tr><tr><td>Остановить все запущенные контейнеры</td><td>docker stop $(docker ps -a -q)</td></tr><tr><td>Удалить все остановленные контейнеры</td><td>docker rm $(docker ps -a -q)</td></tr><tr><td>Остановить контейнер (данные сохраняются)</td><td>docker compose stop</td></tr><tr><td>Остановить и удалить контейнер (данные сохраняются)</td><td>docker compose down</td></tr><tr><td>Перезапустить контейнер (данные сохраняются)</td><td>docker compose restart</td></tr><tr><td>Удалить контейнер mysql_container (данные сохраняются)</td><td>docker rm mysql_container</td></tr><tr><td>Удалить контейнер вместе с данными </td><td>docker compose down -v</td></tr><tr><td>Посмотреть логи контейнера mysql_container</td><td>docker logs -f mysql_container</td></tr><tr><td>Войти в MySQL как root в контейнере mysql_container </td><td>docker exec -it mysql_container mysql -u root -p</td></tr><tr><td>Сделать дамп</td><td>docker exec mysql_container mysqldump -u root -p my_project_db &gt; my_project_db.sql</td></tr><tr><td>Зайти в контейнер mysql_container </td><td>docker exec -it mysql_container  /bin/bash</td></tr><tr><td>Удаление всех контейнеров, образов, сетей</td><td>docker system prune</td></tr><tr><td>Удаление всех  контейнеров, образов, сетей, томов</td><td>docker system prune &#8212;volumes</td></tr><tr><td></td><td></td></tr></tbody></table></figure>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/mysql-in-docker/">Использование MySQL в контейнере Docker</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Zero-downtime деплой через CICD Gitlab</title>
		<link>https://mcodex.ru/deploy-cicd-gitlab/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Wed, 13 May 2026 14:53:05 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=154</guid>

					<description><![CDATA[<p>Пошаговая инструкция по настройке деплоя проектов на VPS через Gitlab CICD</p>
<p>Сообщение <a href="https://mcodex.ru/deploy-cicd-gitlab/">Zero-downtime деплой через CICD Gitlab</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Содержание</strong></p>



<ul class="wp-block-list">
<li><a href="#description" type="internal" id="#description">Описание задачи и требования к проекту</a></li>



<li><a href="#step-1" type="internal" id="#step-1">Начальная настройка проекта</a></li>



<li><a href="#step-2" type="internal" id="#step-2">Пользователи для деплоя</a></li>



<li><a href="#step-3" type="internal" id="#step-3">SSH-ключи</a></li>



<li><a href="#step-4" type="internal" id="#step-4">Переменные Gitlab</a></li>



<li><a href="#step-5" type="internal" id="#step-5">Структура проекта для CICD</a></li>



<li><a href="#step-6" type="internal" id="#step-6">Переключение Nginx на новый проект</a></li>



<li><a href="#step-7" type="internal" id="#step-7">Первый деплой через CICD</a></li>



<li><a href="#step-8" type="internal" id="#step-8">Откат</a></li>
</ul>



<h2 id="description" class="wp-block-heading"><strong>Описание задачи и требования к проекту</strong></h2>



<p class="wp-block-paragraph">Проект разрабатыватся на локальной машине, есть 2 сервера, dev и prodcution.<br>Стэк одинаковый: VPS Ubuntu/Nginx/PHP/MySQL/Laravel/Supervisor</p>



<p class="wp-block-paragraph">Шаги в инструкции выполняются на обеих серверах. В результате получаем 2 развернутых версии проекта:<br>на dev-сервере (ветка develop) и production (ветка main).</p>



<p class="wp-block-paragraph">После применения инструкции все дальнейшие загрузки обновлений происходят без ручного вмешательства на серверах, на dev-сервере &#8212; после пуша в ветку develop, на production &#8212; после пуша (или PR) в main  и клика на кнопке деплоя (опциональный предохранитель).</p>



<p class="wp-block-paragraph">Перед внедрением CICD, необходимо проверить следующие пункты:</p>



<ol class="wp-block-list">
<li>На серверах есть полномочия sudo</li>



<li>В проекте есть тесты</li>



<li>В проекте есть публично доступный роут GET /health/ready, возвращает код 200. </li>



<li> Перед пушем в gitlab прогоняем тесты, линтер, pint.</li>
</ol>



<h2 id="step-1" class="wp-block-heading"><strong>Шаг 1. Начальная настройка проекта</strong></h2>



<p class="wp-block-paragraph">Разворачиваем проекты на серверах из репозитория Gitlab вручную, без CICD, настраиваем Nginx, подключаем СУБД и т.д. Проверяем, что все работает.<br>Предположим, проект развернут в папке  <strong>/var/www/my-project.com/classic-deploy</strong></p>



<p class="wp-block-paragraph">Сразу решаем, в какой папке будет расположен проект, разворачиваемый через CICD.<br>Допустим, он будет в папке <strong>/var/www/my-project.com/cicd-deploy</strong></p>



<h2 id="step-2" class="wp-block-heading"><strong>Шаг 2. Создаем пользователя для деплоя</strong></h2>



<p class="wp-block-paragraph">2.1 На обеих серверах добавляем пользователя <strong>deployer</strong>. Через него Gitlab будет делать деплой.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo adduser deployer
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">adduser</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">2.2 Добавляем пользователя deployer в группу www-data</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo usermod -aG www-data deployer</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">usermod</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">aG</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">www</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span></code></pre></div>



<p class="wp-block-paragraph">Проверяем, что добавлен в группу</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>groups deployer</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">groups</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span></span></code></pre></div>



<p class="wp-block-paragraph">в списке должна быть <strong>www-data</strong></p>



<p class="wp-block-paragraph">2.3 Добавляем возможность запуска от имени deployer определенных команд без пароля.<br> Для этого создаем файл конфига:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo visudo -f /etc/sudoers.d/deploy</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">visudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">sudoers</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">d</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">deploy</span></span></code></pre></div>



<p class="wp-block-paragraph">Добавляем в него строку (версию PHP при необходимости изменить):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>deployer ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl, /usr/bin/systemctl reload php8.5-fpm</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ALL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">ALL</span><span style="color: #D8DEE9FF">) NOPASSWD</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">usr</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">supervisorctl</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">usr</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">php8</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">fpm</span></span></code></pre></div>



<p class="wp-block-paragraph">Перед добавлвением можно проверить, где находятся файлы</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>which supervisorctl
which systemctl</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">which</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">supervisorctl</span></span>
<span class="line"><span style="color: #D8DEE9">which</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">systemctl</span></span></code></pre></div>



<h2 id="step-3" class="wp-block-heading"><strong>Шаг 3. Настраиваем SSH-ключи</strong></h2>



<p class="wp-block-paragraph">Логинимся на сервер как пользователь <strong>deployer</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ssh deployer@ip-address-here</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">ssh</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">deployer</span><span style="color: #D8DEE9FF">@</span><span style="color: #D8DEE9">ip</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">address</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">here</span></span></code></pre></div>



<p class="wp-block-paragraph">Создаем новый ключ, без ключевой фразы, для дев-сервера используем префикс dev</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>ssh-keygen -t ed25519 -C "gitlab-deploy-prod"</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">keygen</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">t</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">ed25519</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">C</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">gitlab-deploy-prod</span><span style="color: #ECEFF4">&quot;</span></span></code></pre></div>



<p class="wp-block-paragraph">Появляется папка /home/deployer/.ssh, в ней файлы ключа id_ed25519.pub и id_ed25519.<br><strong>Имена файлов ключей лучше использовать дефолтные, иначе може возникнуть ошибка при подключении к Gitlab.</strong></p>



<p class="wp-block-paragraph">Добавляем публичный ключ в authorized_keys</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">cat</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">id_ed25519</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">pub</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">authorized_keys</span></span></code></pre></div>



<p class="wp-block-paragraph">Содержимое файла публичного ключа добавляем в ключи в репозитории проекта на Gitlab<br><strong>Settings-&gt;Reposirory-&gt;Deploy keys-&gt;Add new key</strong></p>



<p class="wp-block-paragraph">В названии ключа указываем сервер и пользователя, что бы не путаться, например, &#171;Production deployer&#187; </p>



<p class="wp-block-paragraph">Секретный ключ кодируем в base64</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>base64 -w 0 ~/.ssh/id_ed25519</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">base64</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">w</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ssh</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9">id_ed25519</span></span></code></pre></div>



<p class="wp-block-paragraph">Полученный вывод пока копируем в текстовый файл, он понадобится в следующем шаге.</p>



<p class="wp-block-paragraph">Проверяем,  что ключ работает. Для этого в произвольной папке клонируем репозиторий</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>mkdir ~/test
cd ~/test
git clone git@gitlab.com:your-project.git</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #D8DEE9">test</span></span>
<span class="line"><span style="color: #D8DEE9">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~/</span><span style="color: #D8DEE9">test</span></span>
<span class="line"><span style="color: #D8DEE9">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">clone</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">git</span><span style="color: #D8DEE9FF">@</span><span style="color: #D8DEE9">gitlab</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">com</span><span style="color: #D8DEE9FF">:</span><span style="color: #D8DEE9">your</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">git</span></span></code></pre></div>



<p class="wp-block-paragraph">Если клонирование прошло успешно, тестовую папку можно удалить.</p>



<h2 id="step-4" class="wp-block-heading"><strong>Шаг 4. Задаем переменные Gitlab</strong></h2>



<p class="wp-block-paragraph">В Gitlab, в папке проекта открываем Settings-&gt;CICD-&gt;Variables<br>Создаем переменные: <br><strong>PROD_HOST </strong>&#8212; IP адрес, например, 123.1.123.12<br><strong>PROD_BASE</strong> &#8212; путь к папке cicd-проекта, /var/www/my-project.com/cicd-deploy<br><strong>PROD_APP_URL</strong> &#8212; публичный URL проекта, для запуска health check, например, https://my-project.com. <br><strong>PROD_USER</strong> &#8212; deployer<br><strong>PROD_SSH_PRIVATE_KEY_B64</strong> &#8212; приватный ключ в формате base64 из предыдущего шага<br>Тип переменной &#8212; variable (default), visibility: visible, protect variable &#8212; выключено<br>Для дев-севера аналогично, но с префиксом <strong>DEV_</strong></p>



<p class="wp-block-paragraph">В результате в репозитории есть набор переменных</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="504" src="https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1024x504.png" alt="переменные Gitlab" class="wp-image-167" srcset="https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1024x504.png 1024w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-300x148.png 300w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-768x378.png 768w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars-1536x755.png 1536w, https://mcodex.ru/wp-content/uploads/2026/05/gitlab-vars.png 1570w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph"><strong>Результат на данном этапе:  </strong><br>Проект работает по прежнему из папки classic-deploy, на сервере добавлен пользователь deployer.</p>



<h2 id="step-5" class="wp-block-heading"><strong>Шаг 5. Создаем структуру проекта для CICD</strong></h2>



<p class="wp-block-paragraph">Допустим, проект, развернутый через CICD будет расположен в папке <strong>/var/www/my-project.com/cicd-deploy</strong></p>



<p class="wp-block-paragraph">В этом случае структура downtime-zero проекта будет следующей</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>/var/www/my-porject.com/cicd-deploy/
  ├── current -> releases/20260501-1430-abc1234 ← симлинк на активный релиз
  ├── releases/
  │ ├── 20260430-1731-2b5cc17/
  │ ├── 20260501-0900-69025b4/
  │ └── 20260501-1430-abc1234/ ← текущий
  └── shared/
    ├── .env
    └── storage/</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">/var</span><span style="color: #D8DEE9FF">/</span><span style="color: #D8DEE9">www</span><span style="color: #D8DEE9FF">/my-porject.com/cicd-deploy/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  ├── </span><span style="color: #D8DEE9">current</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">releases</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1430</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">abc1234</span><span style="color: #D8DEE9FF"> ← </span><span style="color: #D8DEE9">симлинк</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">на</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">активный</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">релиз</span></span>
<span class="line"><span style="color: #D8DEE9FF">  ├── </span><span style="color: #D8DEE9">releases</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ ├── </span><span style="color: #B48EAD">20260430</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1731</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">2</span><span style="color: #D8DEE9">b5cc17</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ ├── </span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">0900</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">69025</span><span style="color: #D8DEE9">b4</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">  │ └── </span><span style="color: #B48EAD">20260501</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1430</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9">abc1234</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF"> ← </span><span style="color: #D8DEE9">текущий</span></span>
<span class="line"><span style="color: #D8DEE9FF">  └── </span><span style="color: #D8DEE9">shared</span><span style="color: #81A1C1">/</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ├── </span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">env</span></span>
<span class="line"><span style="color: #D8DEE9FF">    └── </span><span style="color: #D8DEE9">storage</span><span style="color: #81A1C1">/</span></span></code></pre></div>



<p class="wp-block-paragraph">5.1 Делаем первый деплой вручную.</p>



<p class="wp-block-paragraph">Логинимся под <strong>root</strong>, создаем папку ~/deploy, в нее кладем файл<strong> first_deploy.sh</strong></p>



<p class="wp-block-paragraph">Заменить: <br>BASE &#8212; путь к новой папке проекта <br>OLD_PATH &#8212; текущий проект<br>REPO &#8212; репозиторий Gitlab</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>#!/usr/bin/env bash
umask 0002
set -euo pipefail

BASE=/var/www/my-project.com/cicd-deploy
OLD_PATH=/var/www/my-porject.com/classic-deploy
SHARED=$BASE/shared
TS=$(date +%Y%m%d-%H%M)
REL=$BASE/releases/${TS}-manual
REPO=git@gitlab.com:your-project-repo.git

mkdir -p $BASE/releases
mkdir -p $BASE/shared/storage/{app/public,framework/cache/data,framework/sessions,framework/testing,framework/views,logs}
sudo chown -R deployer:www-data $BASE
sudo find $BASE -type d -exec chmod 2775 {} \;
sudo find $BASE -type f -exec chmod 0664 {} \;

cp -a $OLD_PATH/.env $BASE/shared/.env
cp -a $OLD_PATH/storage/. $BASE/shared/storage/
sudo chown -R deployer:www-data $BASE/shared
sudo find $BASE/shared -type d -exec chmod 2775 {} \;
sudo find $BASE/shared -type f -exec chmod 0664 {} \;
sudo chmod 0640 $BASE/shared/.env

sudo -u deployer git clone --depth 1 --branch develop "$REPO" "$REL"

sudo -u deployer ln -sfn "$SHARED/.env" "$REL/.env"
sudo -u deployer rm -rf "$REL/storage"
sudo -u deployer ln -sfn "$SHARED/storage" "$REL/storage"

# на production-сервере добавить опцию --no-dev
sudo -u deployer bash -c "cd '$REL' &amp;&amp; composer install --optimize-autoloader --no-interaction"
sudo -u deployer php "$REL/artisan" storage:link
sudo -u deployer php "$REL/artisan" config:cache
sudo -u deployer php "$REL/artisan" route:cache
sudo -u deployer php "$REL/artisan" view:cache
sudo -u deployer php "$REL/artisan" event:cache
sudo -u deployer php "$REL/artisan" migrate --force

sudo rm -rf "$BASE/current.new"
sudo -u deployer ln -sfn "$REL" "$BASE/current.new"
sudo -u deployer mv -Tf "$BASE/current.new" "$BASE/current"
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">#!/usr/bin/env bash</span></span>
<span class="line"><span style="color: #88C0D0">umask</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0002</span></span>
<span class="line"><span style="color: #88C0D0">set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-euo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">pipefail</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">BASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy</span></span>
<span class="line"><span style="color: #D8DEE9">OLD_PATH</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-porject.com/classic-deploy</span></span>
<span class="line"><span style="color: #D8DEE9">SHARED</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span></span>
<span class="line"><span style="color: #D8DEE9">TS</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">date</span><span style="color: #A3BE8C"> +%Y%m%d-%H%M</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9">REL</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases/</span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">TS</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">-manual</span></span>
<span class="line"><span style="color: #D8DEE9">REPO</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">git@gitlab.com:your-project-repo.git</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases</span></span>
<span class="line"><span style="color: #88C0D0">mkdir</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-p</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage/{app/public,framework/cache/data,framework/sessions,framework/testing,framework/views,logs}</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chown</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer:www-data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2775</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0664</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">cp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$OLD_PATH</span><span style="color: #A3BE8C">/.env</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/.env</span></span>
<span class="line"><span style="color: #88C0D0">cp</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$OLD_PATH</span><span style="color: #A3BE8C">/storage/.</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/storage/</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chown</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-R</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer:www-data</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">d</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2775</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-type</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">f</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-exec</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0664</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">{}</span><span style="color: #D8DEE9FF"> </span><span style="color: #EBCB8B">\;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0640</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/shared/.env</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">git</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">clone</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--depth</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--branch</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">develop</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REPO</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$SHARED</span><span style="color: #A3BE8C">/.env</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/.env</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rm</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$SHARED</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/storage</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># на production-сервере добавить опцию --no-dev</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-c</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">cd &#39;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">&#39; &amp;&amp; composer install --optimize-autoloader --no-interaction</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">storage:link</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">config:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">route:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">view:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">event:cache</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #A3BE8C">/artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">migrate</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rm</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-rf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$REL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-u</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deployer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mv</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-Tf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Что делает скрипт: </p>



<ul class="wp-block-list">
<li>Создает структуру проекта</li>



<li>Копирует shared-файлы из текущего проекта</li>



<li>Создает первый релиз с префиксом manual</li>



<li>Устанавливает права </li>
</ul>



<p class="wp-block-paragraph">Перед запуском проверяем текущую версию CLI PHP, она должна соответствовать требованиям пакетов в composer.lock, иначе возникнет ошибка при установке пакетов.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>php -v</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-v</span></span></code></pre></div>



<p class="wp-block-paragraph">При необходимости, переключаем на нужную версию:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>sudo update-alternatives --set php /usr/bin/php8.5</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">update-alternatives</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/usr/bin/php8.5</span></span></code></pre></div>



<p class="wp-block-paragraph">Запускаем скрипт</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>cd ~/deploy
bash first_deploy.sh</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">~/deploy</span></span>
<span class="line"><span style="color: #88C0D0">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">first_deploy.sh</span></span></code></pre></div>



<p class="wp-block-paragraph">В результате в папке var/www/my-project.com/cicd-deploy создается структура ciccd-проекта, но он пока не работает, так как Nginx отправляет запросы на старый проект.</p>



<h2 id="step-6" class="wp-block-heading"><strong>Шаг 6. Переключаем Nginx на новый проект</strong></h2>



<p class="wp-block-paragraph">6.1 В файле конфигурации Nginx меняем путь для root на <br>/var/www/my-project.com/cicd-deploy/current/public</p>



<p class="wp-block-paragraph">Перезагружаем Nginx </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>nginx -t
systemctl reload nginx </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">nginx</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-t</span></span>
<span class="line"><span style="color: #88C0D0">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">nginx</span><span style="color: #D8DEE9FF"> </span></span></code></pre></div>



<p class="wp-block-paragraph">6.2 Меняем конфигурацию supervisor</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>nano /etc/supervisor/conf.d/horizon.conf

## меняем пути  
command=php /var/www/my-project.com/cicd-deploy/current/artisan horizon
directory=/var/www/my-project.com/cicd-deploy/current
stdout_logfile=/var/www/my-project.com/cicd-deploy/shared/storage/logs/horizon.log

supervisorctl reread &amp;&amp; supervisorctl update
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">nano</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">/etc/supervisor/conf.d/horizon.conf</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">## меняем пути  </span></span>
<span class="line"><span style="color: #D8DEE9">command</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">/var/www/my-project.com/cicd-deploy/current/artisan</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon</span></span>
<span class="line"><span style="color: #D8DEE9">directory</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy/current</span></span>
<span class="line"><span style="color: #D8DEE9">stdout_logfile</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">/var/www/my-project.com/cicd-deploy/shared/storage/logs/horizon.log</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reread</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">update</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>6.3 Важно: не забыть проверить и заменить пути в заданиях cron, пути (если есть) в файле .env, в файле конфигураций.</strong></p>



<p class="wp-block-paragraph">Теперь проект работает из новой папки, релиз -manual.</p>



<p class="wp-block-paragraph">Проверяем, что все работает, адрес /health/ready возвращает JSON с 200</p>



<h2 id="step-7" class="wp-block-heading"><strong>Шаг 7. Делаем первый деплой через Gitlab CICD Pipeline.</strong></h2>



<p class="wp-block-paragraph">7.1 На локальном сервере добавляем в корневую проекта файл пайплайна <strong>.gitlab-ci.yaml</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>stages:
  - test
  - deploy

# ──────────────── ТЕСТЫ ────────────────
test:
  stage: test
  image: php:8.5-cli
  cache:
    key:
      files: &#91;composer.lock&#93;
    paths: &#91;vendor/&#93;
  variables:
    DB_CONNECTION: sqlite
    DB_DATABASE: ":memory:"
  before_script:
    - apt-get update -qq &amp;&amp; apt-get install -y -qq git unzip libicu-dev libzip-dev libsqlite3-dev
    - docker-php-ext-install intl pcntl zip pdo_sqlite
    - curl -sS https://getcomposer.org/installer | php
    - php composer.phar install --prefer-dist --no-ansi --no-progress
    - cp .env.example .env
    - php artisan key:generate
  script:
    - php artisan test --compact
  rules:
    - if: $CI_COMMIT_BRANCH == "main" # запускаем тест при пуше в main
    - if: $CI_COMMIT_BRANCH == "develop" # запускаем тест при пуше в develop
    - if: $CI_PIPELINE_SOURCE == "merge_request_event" # запускаем тест при PR
# тесты не запускаются при пуше в feature-ветки

# ──────────────── ДЕПЛОЙ НА DEV ────────────────
deploy:dev:
  stage: deploy
  needs: &#91;test&#93;
  image: alpine:latest
  variables:
    KEEP_RELEASES: "5"
    HEALTH_URL: "${DEV_APP_URL}/health/ready"
  script:
    - apk add --no-cache openssh-client curl
    - mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh
    - echo "$DEV_SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_deploy
    - chmod 600 ~/.ssh/id_deploy
    - ssh-keyscan -H "$DEV_HOST" >> ~/.ssh/known_hosts
    - |
      ssh -i ~/.ssh/id_deploy "$DEV_USER@$DEV_HOST" \
        "BASE='$DEV_BASE' SHA='$CI_COMMIT_SHORT_SHA' FULL_SHA='$CI_COMMIT_SHA' REPO='$CI_REPOSITORY_URL' KEEP_RELEASES='$KEEP_RELEASES' bash -se" &lt;&lt;'EOSSH'
        set -euo pipefail
        umask 0002
        TS=$(date +%Y%m%d-%H%M)
        REL="$BASE/releases/${TS}-${SHA}"
        SHARED="$BASE/shared"

        git config --global --add safe.directory "$REL"

        # 1. Клонируем релиз (shallow) от develop
        git clone --depth 1 --branch develop "$REPO" "$REL"
        cd "$REL"
        echo "$FULL_SHA" > RELEASE

        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)
        mkdir -p "$SHARED/storage/app/public" \
                 "$SHARED/storage/framework/cache/data" \
                 "$SHARED/storage/framework/sessions" \
                 "$SHARED/storage/framework/testing" \
                 "$SHARED/storage/framework/views" \
                 "$SHARED/storage/logs"

        # 3. Линкуем shared
        ln -sfn "$SHARED/.env" .env
        rm -rf storage &amp;&amp; ln -sfn "$SHARED/storage" storage

        # 3. Установка зависимостей (на новом релизе, не трогая current)
        composer install --optimize-autoloader --no-interaction --no-progress

        # 4. Symlink public/storage -> storage/app/public
        php artisan storage:link

        # 5. Кеши конфигов/роутов/вью — в новом релизе
        php artisan config:cache
        php artisan route:cache
        php artisan view:cache
        php artisan event:cache

        # 5. Миграции (один раз на релиз; делаем ДО переключения)
        php artisan migrate --force

        # 6. Атомарное переключение symlink current
        ln -sfn "$REL" "$BASE/current.new"
        mv -Tf "$BASE/current.new" "$BASE/current"

        # 7. Перезапуск Horizon (graceful)
        sudo supervisorctl signal SIGTERM horizon:* || php artisan horizon:terminate

        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)
        sudo systemctl reload php8.5-fpm

        # 9. Чистим старые релизы
        cd "$BASE/releases" &amp;&amp; ls -1t | tail -n +$((KEEP_RELEASES+1)) | xargs -r rm -rf
      EOSSH
    # 10. Health check после деплоя
    - |
      echo "Health check: $HEALTH_URL"
      for i in 1 2 3 4 5; do
        code=$(curl -s -o /tmp/health.out -w "%{http_code}" "$HEALTH_URL" || echo "000")
        echo "attempt $i: HTTP $code"
        if &#91; "$code" = "200" &#93;; then
          cat /tmp/health.out; echo
          exit 0
        fi
        sleep 3
      done
      echo "Health check failed"; cat /tmp/health.out 2>/dev/null || true
      exit 1
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"

# ──────────────── ДЕПЛОЙ НА PROD ────────────────
deploy:prod:
  stage: deploy
  when: manual
  needs: &#91;test&#93;
  image: alpine:latest
  variables:
    KEEP_RELEASES: "5"
    HEALTH_URL: "${PROD_APP_URL}/health/ready"
  script:
    - apk add --no-cache openssh-client curl
    - mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh
    - echo "$PROD_SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_deploy
    - chmod 600 ~/.ssh/id_deploy
    - ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts
    - |
      ssh -i ~/.ssh/id_deploy "$PROD_USER@$PROD_HOST" \
        "BASE='$PROD_BASE' SHA='$CI_COMMIT_SHORT_SHA' FULL_SHA='$CI_COMMIT_SHA' REPO='$CI_REPOSITORY_URL' KEEP_RELEASES='$KEEP_RELEASES' bash -se" &lt;&lt;'EOSSH'
        set -euo pipefail
        umask 0002
        TS=$(date +%Y%m%d-%H%M)
        REL="$BASE/releases/${TS}-${SHA}"
        SHARED="$BASE/shared"

        git config --global --add safe.directory "$REL"

        # 1. Клонируем релиз (shallow) от main
        git clone --depth 1 --branch main "$REPO" "$REL"
        cd "$REL"
        echo "$FULL_SHA" > RELEASE

        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)
        mkdir -p "$SHARED/storage/app/public" \
                 "$SHARED/storage/framework/cache/data" \
                 "$SHARED/storage/framework/sessions" \
                 "$SHARED/storage/framework/testing" \
                 "$SHARED/storage/framework/views" \
                 "$SHARED/storage/logs"

        # 3. Линкуем shared
        ln -sfn "$SHARED/.env" .env
        rm -rf storage &amp;&amp; ln -sfn "$SHARED/storage" storage

        # 3. Установка зависимостей (на новом релизе, не трогая current)
        composer install --no-dev --optimize-autoloader --no-interaction --no-progress

        # 4. Symlink public/storage -> storage/app/public
        php artisan storage:link

        # 5. Кеши конфигов/роутов/вью — в новом релизе
        php artisan config:cache
        php artisan route:cache
        php artisan view:cache
        php artisan event:cache

        # 5. Миграции (один раз на релиз; делаем ДО переключения)
        php artisan migrate --force

        # 6. Атомарное переключение symlink current
        ln -sfn "$REL" "$BASE/current.new"
        mv -Tf "$BASE/current.new" "$BASE/current"

        # 7. Перезапуск Horizon (graceful)
        sudo supervisorctl signal SIGTERM horizon:* || php artisan horizon:terminate

        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)
        sudo systemctl reload php8.5-fpm

        # 9. Чистим старые релизы
        cd "$BASE/releases" &amp;&amp; ls -1t | tail -n +$((KEEP_RELEASES+1)) | xargs -r rm -rf
      EOSSH
    # 10. Health check после деплоя
    - |
      echo "Health check: $HEALTH_URL"
      for i in 1 2 3 4 5; do
        code=$(curl -s -o /tmp/health.out -w "%{http_code}" "$HEALTH_URL" || echo "000")
        echo "attempt $i: HTTP $code"
        if &#91; "$code" = "200" &#93;; then
          cat /tmp/health.out; echo
          exit 0
        fi
        sleep 3
      done
      echo "Health check failed"; cat /tmp/health.out 2>/dev/null || true
      exit 1
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #8FBCBB">stages</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">test</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ТЕСТЫ ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">test</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">test</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php:8.5-cli</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">cache</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">key</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #8FBCBB">files</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">composer.lock</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">paths</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">vendor/</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">DB_CONNECTION</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">sqlite</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">DB_DATABASE</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">:memory:</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">before_script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apt-get update -qq &amp;&amp; apt-get install -y -qq git unzip libicu-dev libzip-dev libsqlite3-dev</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">docker-php-ext-install intl pcntl zip pdo_sqlite</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">curl -sS https://getcomposer.org/installer | php</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php composer.phar install --prefer-dist --no-ansi --no-progress</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">cp .env.example .env</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php artisan key:generate</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php artisan test --compact</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;main&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при пуше в main</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;develop&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при пуше в develop</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># запускаем тест при PR</span></span>
<span class="line"><span style="color: #616E88"># тесты не запускаются при пуше в feature-ветки</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ДЕПЛОЙ НА DEV ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">deploy:dev</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">needs</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">alpine:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">KEEP_RELEASES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">HEALTH_URL</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">${DEV_APP_URL}/health/ready</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apk add --no-cache openssh-client curl</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">echo &quot;$DEV_SSH_PRIVATE_KEY_B64&quot; | base64 -d &gt; ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod 600 ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssh-keyscan -H &quot;$DEV_HOST&quot; &gt;&gt; ~/.ssh/known_hosts</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      ssh -i ~/.ssh/id_deploy &quot;$DEV_USER@$DEV_HOST&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">        &quot;BASE=&#39;$DEV_BASE&#39; SHA=&#39;$CI_COMMIT_SHORT_SHA&#39; FULL_SHA=&#39;$CI_COMMIT_SHA&#39; REPO=&#39;$CI_REPOSITORY_URL&#39; KEEP_RELEASES=&#39;$KEEP_RELEASES&#39; bash -se&quot; &lt;&lt;&#39;EOSSH&#39;</span></span>
<span class="line"><span style="color: #A3BE8C">        set -euo pipefail</span></span>
<span class="line"><span style="color: #A3BE8C">        umask 0002</span></span>
<span class="line"><span style="color: #A3BE8C">        TS=$(date +%Y%m%d-%H%M)</span></span>
<span class="line"><span style="color: #A3BE8C">        REL=&quot;$BASE/releases/${TS}-${SHA}&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        SHARED=&quot;$BASE/shared&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        git config --global --add safe.directory &quot;$REL&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 1. Клонируем релиз (shallow) от develop</span></span>
<span class="line"><span style="color: #A3BE8C">        git clone --depth 1 --branch develop &quot;$REPO&quot; &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;$FULL_SHA&quot; &gt; RELEASE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)</span></span>
<span class="line"><span style="color: #A3BE8C">        mkdir -p &quot;$SHARED/storage/app/public&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/cache/data&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/sessions&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/testing&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/views&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/logs&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Линкуем shared</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$SHARED/.env&quot; .env</span></span>
<span class="line"><span style="color: #A3BE8C">        rm -rf storage &amp;&amp; ln -sfn &quot;$SHARED/storage&quot; storage</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Установка зависимостей (на новом релизе, не трогая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        composer install --optimize-autoloader --no-interaction --no-progress</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 4. Symlink public/storage -&gt; storage/app/public</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan storage:link</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Кеши конфигов/роутов/вью — в новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan config:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan route:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan view:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan event:cache</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Миграции (один раз на релиз; делаем ДО переключения)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan migrate --force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 6. Атомарное переключение symlink current</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$REL&quot; &quot;$BASE/current.new&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        mv -Tf &quot;$BASE/current.new&quot; &quot;$BASE/current&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 7. Перезапуск Horizon (graceful)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo supervisorctl signal SIGTERM horizon:* || php artisan horizon:terminate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo systemctl reload php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 9. Чистим старые релизы</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$BASE/releases&quot; &amp;&amp; ls -1t | tail -n +$((KEEP_RELEASES+1)) | xargs -r rm -rf</span></span>
<span class="line"><span style="color: #A3BE8C">      EOSSH</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88"># 10. Health check после деплоя</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check: $HEALTH_URL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">      for i in 1 2 3 4 5; do</span></span>
<span class="line"><span style="color: #A3BE8C">        code=$(curl -s -o /tmp/health.out -w &quot;%{http_code}&quot; &quot;$HEALTH_URL&quot; || echo &quot;000&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;attempt $i: HTTP $code&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; &quot;$code&quot; = &quot;200&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          cat /tmp/health.out; echo</span></span>
<span class="line"><span style="color: #A3BE8C">          exit 0</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"><span style="color: #A3BE8C">        sleep 3</span></span>
<span class="line"><span style="color: #A3BE8C">      done</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check failed&quot;; cat /tmp/health.out 2&gt;/dev/null || true</span></span>
<span class="line"><span style="color: #A3BE8C">      exit 1</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;develop&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># ──────────────── ДЕПЛОЙ НА PROD ────────────────</span></span>
<span class="line"><span style="color: #8FBCBB">deploy:prod</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">stage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">when</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">manual</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">needs</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #A3BE8C">test</span><span style="color: #ECEFF4">&#93;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">alpine:latest</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">variables</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">KEEP_RELEASES</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">5</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">HEALTH_URL</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">${PROD_APP_URL}/health/ready</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">script</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">apk add --no-cache openssh-client curl</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">echo &quot;$PROD_SSH_PRIVATE_KEY_B64&quot; | base64 -d &gt; ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">chmod 600 ~/.ssh/id_deploy</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ssh-keyscan -H &quot;$PROD_HOST&quot; &gt;&gt; ~/.ssh/known_hosts</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      ssh -i ~/.ssh/id_deploy &quot;$PROD_USER@$PROD_HOST&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">        &quot;BASE=&#39;$PROD_BASE&#39; SHA=&#39;$CI_COMMIT_SHORT_SHA&#39; FULL_SHA=&#39;$CI_COMMIT_SHA&#39; REPO=&#39;$CI_REPOSITORY_URL&#39; KEEP_RELEASES=&#39;$KEEP_RELEASES&#39; bash -se&quot; &lt;&lt;&#39;EOSSH&#39;</span></span>
<span class="line"><span style="color: #A3BE8C">        set -euo pipefail</span></span>
<span class="line"><span style="color: #A3BE8C">        umask 0002</span></span>
<span class="line"><span style="color: #A3BE8C">        TS=$(date +%Y%m%d-%H%M)</span></span>
<span class="line"><span style="color: #A3BE8C">        REL=&quot;$BASE/releases/${TS}-${SHA}&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        SHARED=&quot;$BASE/shared&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        git config --global --add safe.directory &quot;$REL&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 1. Клонируем релиз (shallow) от main</span></span>
<span class="line"><span style="color: #A3BE8C">        git clone --depth 1 --branch main &quot;$REPO&quot; &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$REL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;$FULL_SHA&quot; &gt; RELEASE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 2. Гарантируем структуру shared (на случай первого деплоя/чистого shared)</span></span>
<span class="line"><span style="color: #A3BE8C">        mkdir -p &quot;$SHARED/storage/app/public&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/cache/data&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/sessions&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/testing&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/framework/views&quot; \</span></span>
<span class="line"><span style="color: #A3BE8C">                 &quot;$SHARED/storage/logs&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Линкуем shared</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$SHARED/.env&quot; .env</span></span>
<span class="line"><span style="color: #A3BE8C">        rm -rf storage &amp;&amp; ln -sfn &quot;$SHARED/storage&quot; storage</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 3. Установка зависимостей (на новом релизе, не трогая current)</span></span>
<span class="line"><span style="color: #A3BE8C">        composer install --no-dev --optimize-autoloader --no-interaction --no-progress</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 4. Symlink public/storage -&gt; storage/app/public</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan storage:link</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Кеши конфигов/роутов/вью — в новом релизе</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan config:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan route:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan view:cache</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan event:cache</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 5. Миграции (один раз на релиз; делаем ДО переключения)</span></span>
<span class="line"><span style="color: #A3BE8C">        php artisan migrate --force</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 6. Атомарное переключение symlink current</span></span>
<span class="line"><span style="color: #A3BE8C">        ln -sfn &quot;$REL&quot; &quot;$BASE/current.new&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        mv -Tf &quot;$BASE/current.new&quot; &quot;$BASE/current&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 7. Перезапуск Horizon (graceful)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo supervisorctl signal SIGTERM horizon:* || php artisan horizon:terminate</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 8. Reload php-fpm для сброса opcache (graceful, без downtime)</span></span>
<span class="line"><span style="color: #A3BE8C">        sudo systemctl reload php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">        # 9. Чистим старые релизы</span></span>
<span class="line"><span style="color: #A3BE8C">        cd &quot;$BASE/releases&quot; &amp;&amp; ls -1t | tail -n +$((KEEP_RELEASES+1)) | xargs -r rm -rf</span></span>
<span class="line"><span style="color: #A3BE8C">      EOSSH</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88"># 10. Health check после деплоя</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check: $HEALTH_URL&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">      for i in 1 2 3 4 5; do</span></span>
<span class="line"><span style="color: #A3BE8C">        code=$(curl -s -o /tmp/health.out -w &quot;%{http_code}&quot; &quot;$HEALTH_URL&quot; || echo &quot;000&quot;)</span></span>
<span class="line"><span style="color: #A3BE8C">        echo &quot;attempt $i: HTTP $code&quot;</span></span>
<span class="line"><span style="color: #A3BE8C">        if &#91; &quot;$code&quot; = &quot;200&quot; &#93;; then</span></span>
<span class="line"><span style="color: #A3BE8C">          cat /tmp/health.out; echo</span></span>
<span class="line"><span style="color: #A3BE8C">          exit 0</span></span>
<span class="line"><span style="color: #A3BE8C">        fi</span></span>
<span class="line"><span style="color: #A3BE8C">        sleep 3</span></span>
<span class="line"><span style="color: #A3BE8C">      done</span></span>
<span class="line"><span style="color: #A3BE8C">      echo &quot;Health check failed&quot;; cat /tmp/health.out 2&gt;/dev/null || true</span></span>
<span class="line"><span style="color: #A3BE8C">      exit 1</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #8FBCBB">rules</span><span style="color: #ECEFF4">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">$CI_COMMIT_BRANCH == &quot;main&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Необходимо убедится, что адрес /health/ready доступен, возвращает JSON с 200.</strong></p>



<p class="wp-block-paragraph">7<strong>.2 Тестируем пайплайн</strong></p>



<p class="wp-block-paragraph">Сначала проверяем дев-сервер.</p>



<p class="wp-block-paragraph">На локальной машине  в ветке develop делаем правки, коммитим  и пушим в репозиторий Gitlab, в удаленную ветку develop.<br>Gitlab запускает пайплайн для develop, посмотреть можно в резделе Project-&gt;Build-&gt;Pipelines.<br>Сначала выполняется задача с тестами, затем задача с деплоем на дев-сервер. Если все успешно &#8212; появляется зеленая метка Passed. Проверяем на дев-сервере, что обновление загрузилось.</p>



<p class="wp-block-paragraph">Если какая-либо задача в пайплайне завершилась с ошибкой &#8212; появится метка Failed, нажав на нее можно увидеть какая именно задача зафейлилась, нажав на задачу можно посмотреть лог, в чем именно ошибка. Там же можно перезапустить задачу, если исправление ошибки не требует  нового коммита.</p>



<p class="wp-block-paragraph"><strong>Теперь проверяем деплой на production.</strong></p>



<p class="wp-block-paragraph">Текущее состояние production перед тестом:</p>



<ul class="wp-block-list">
<li>две папки проектов: /classic-deploy и /cicd-deploy</li>



<li>проект работает из папки /cicd-deploy, current указывает на релиз &#8216;-manual&#8217;</li>
</ul>



<p class="wp-block-paragraph">Для запуска пайплайна в удаленную ветку main необходимо загрузить коммит слияния с веткой develop.</p>



<p class="wp-block-paragraph">Есть 2 варианта, можно выбрать любой:<br>a. Смерджить в локальном репозитории develop в main и затем запушить main в удаленный.<br>b. Смерджить ветку develop в main в самом Gitlab.</p>



<p class="wp-block-paragraph">Выбираем вариант b. <br>Переходим Project-&gt;Code-&gt;Merge request, нажимаем Create merge request<br>Появляется надпись Ready to merge,  перед слиянием запускается задача пайплайна с тестом. <br></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="214" src="https://mcodex.ru/wp-content/uploads/2026/05/merge-1024x214.png" alt="" class="wp-image-209" srcset="https://mcodex.ru/wp-content/uploads/2026/05/merge-1024x214.png 1024w, https://mcodex.ru/wp-content/uploads/2026/05/merge-300x63.png 300w, https://mcodex.ru/wp-content/uploads/2026/05/merge-768x161.png 768w, https://mcodex.ru/wp-content/uploads/2026/05/merge-1536x321.png 1536w, https://mcodex.ru/wp-content/uploads/2026/05/merge.png 1635w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Если тесты проходят, ветки сливаются, &#8216;Ready to merge&#8217; меняется на &#8216;mered by &#8230; &#8216; и запускается заново пайплайн. <br>Снова проходит задача с тестом, а задача деплоя на прод, в отличие от задачи деплоя на  dev-сервер, не запускается, а пропускается, становится статус &#8216;passed&#8217;.  Это защита от случайного пуша в main, в пайплайне в задаче деплоя на прод стоит опция when:manual, то есть нужен ручной запуск. Заходим в задачу и нажимаем кнопку Run.</p>



<p class="wp-block-paragraph">После завершения задач на проде появляется новый релиз, current переключен на него.</p>



<p class="wp-block-paragraph">Проверяем, что проект работает, после этого папку со старым вариантом деплоя (classic-deploy) можно удалить.</p>



<h2 id="step-8" class="wp-block-heading"><strong>Шаг 8. Проверяем откат</strong></h2>



<p class="wp-block-paragraph">Что бы вернутся на предыдущий релиз, необходимо переключить ссылку current на папку предыдущего релиза. </p>



<p class="wp-block-paragraph">Для этого можно использовать следующий скрипт, </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>#!/bin/bash
set -euo pipefail
BASE=//var/www/my-project.com/cicd-deploy

cd "$BASE/releases"

PREV=$(ls -1t | sed -n '2p')   # второй сверху = предыдущий
&#91; -z "$PREV" &#93; &amp;&amp; { echo "No previous release"; exit 1; }

echo "Rolling back to: $PREV"
ln -sfn "$BASE/releases/$PREV" "$BASE/current.new"
mv -Tf "$BASE/current.new" "$BASE/current"

sudo supervisorctl signal SIGTERM horizon:* || true
sudo systemctl reload php8.5-fpm

echo "Done."
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"><span style="color: #88C0D0">set</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-euo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">pipefail</span></span>
<span class="line"><span style="color: #D8DEE9">BASE</span><span style="color: #81A1C1">=</span><span style="color: #A3BE8C">//var/www/my-project.com/cicd-deploy</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">PREV</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">ls</span><span style="color: #A3BE8C"> -1t </span><span style="color: #81A1C1">|</span><span style="color: #A3BE8C"> </span><span style="color: #88C0D0">sed</span><span style="color: #A3BE8C"> -n </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2p</span><span style="color: #ECEFF4">&#39;)</span><span style="color: #D8DEE9FF">   </span><span style="color: #616E88"># второй сверху = предыдущий</span></span>
<span class="line"><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-z</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">No previous release</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">exit</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Rolling back to: </span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">ln</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-sfn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/releases/</span><span style="color: #D8DEE9">$PREV</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">mv</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-Tf</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current.new</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$BASE</span><span style="color: #A3BE8C">/current</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">supervisorctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">signal</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">SIGTERM</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">horizon:</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">true</span></span>
<span class="line"><span style="color: #88C0D0">sudo</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">systemctl</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">reload</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php8.5-fpm</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Done.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Файл скрипта rollback.sh можно положить в папку ~/home/deployer/deploy_scripts/my-project/ и запускать командой </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>bash rollback.sh</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">bash</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">rollback.sh</span></span></code></pre></div>



<p class="wp-block-paragraph"><strong>Данный скрипт не выполняет откаты миграции, поэтому, при необходимости их нужно выполнить вручную, в папке последнего релиза.</strong></p>
<p>Сообщение <a href="https://mcodex.ru/deploy-cicd-gitlab/">Zero-downtime деплой через CICD Gitlab</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Что такое PHP? Какие основные области применения?</title>
		<link>https://mcodex.ru/what-is-php/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 16:59:34 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Собеседование]]></category>
		<category><![CDATA[Базовый уровень]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=150</guid>

					<description><![CDATA[<p>Аббревиатура PHP расшифровывается как &#171;PHP Hypertext Preprocessor&#187;, в названии заложена рекурсия, но изначально язык назывался проще &#8212; &#171;Personal Home Page Tools&#187;.Язык появился в середине 90-х годов 20-го века, является серверным языком программирования общего назначения, преимущественно используемый в веб-разработке.Изначально PHP был создан для работы с веб-страницами, а конкретнее &#8212; для вывода на статических html-страницах динамических данных: ... <a title="Что такое PHP? Какие основные области применения?" class="read-more" href="https://mcodex.ru/what-is-php/" aria-label="Прочитать больше о Что такое PHP? Какие основные области применения?">Читать далее</a></p>
<p>Сообщение <a href="https://mcodex.ru/what-is-php/">Что такое PHP? Какие основные области применения?</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Аббревиатура PHP расшифровывается как &#171;PHP Hypertext Preprocessor&#187;, в названии заложена рекурсия, но изначально язык назывался  проще &#8212; &#171;Personal Home Page Tools&#187;.<br>Язык появился в середине 90-х годов 20-го века, является серверным языком программирования общего назначения, преимущественно используемый в веб-разработке.<br>Изначально PHP был создан для работы с веб-страницами, а конкретнее &#8212; для вывода на статических html-страницах динамических данных: результатов выполнения функций, данных полученных из базы данных и т.д.<br>В настоящее время основной областью применения PHP остаются веб-технологии:</p>



<ul class="wp-block-list">
<li>фреймворки (например Laravel, Symfony, Yii)</li>



<li>СMS (например, WordPress, Drupal, Joomla)</li>



<li>веб-сервисы SaaS</li>



<li>API для фронтенда, мобильных приложений</li>



<li>динамические веб-страницы,</li>



<li>консольные CLI-утилиты (например, Deployer, Composer)</li>
</ul>



<p class="wp-block-paragraph">Основные особенности языка:</p>



<ul class="wp-block-list">
<li>язык является интерпретируемым, но начиная с версии 8.0 у него появился JIT-компилятор</li>



<li>выполнение на сервере, хотя, к слову, технология WebAssembly позволяет выполнять код PHP в браузере, но этот подход имеет узкую специализацию, в основном для<br>создания онлайн-песочниц</li>



<li>нет нативной реализации EventLoop из коробки для реализации асинхронности, но решается через сторонние библиотеки, например, ReactPHP, Swoole</li>



<li>нет многопоточности из коробки, как у Java или Golang, решается через расширения, например, parallel</li>



<li>в традиционном использовании 1 http-запрос = 1 процесс, решается через менеджер процессов PHP-FPM</li>



<li>слабая типизация, но начиная с 7-й версии появилась возможность установить режим строгой типизации</li>
</ul>



<p class="wp-block-paragraph">Сильные стороны языка:</p>



<ul class="wp-block-list">
<li>OPCache &#8212; in-memory хранилище байт-кода. Код приложения преобразовывается интерпретатором в инструкции для виртуальной машины Zend (байт-код, опкод),<br>сохраняется в памяти и при последующих запусках Zend сразу берет готовый байт-код, минуя этапы чтения и преобразования кода приложения.<br>Начиная с версии 5.5 входит в ядро, но для работы необходимо установить опцию в файле php.ini</li>



<li>JIT-компилятор. Работает поверх OPCache, преобразовывает байт-код в машинные инструкции для процессора.</li>



<li>Composer. Менеджер пакетов, сторонняя утилита, позволяет устанавливать в проект пакеты, контролирует зависимости и многое другое.</li>



<li>PSR. Набор стандартов для разработчиков, делающий код проектов удобным для поддержки и использования.</li>
</ul>



<p class="wp-block-paragraph">Области, где PHP не является оптимальным выбором: асинхронные операции, хайлоад, обработка больших объемов данных, но многие проблемы языка можно<br>современными решениями.</p>
<p>Сообщение <a href="https://mcodex.ru/what-is-php/">Что такое PHP? Какие основные области применения?</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Шаблон &#171;Посетитель (Visitor)&#187;</title>
		<link>https://mcodex.ru/pattern-visitor/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Sun, 01 Feb 2026 16:46:22 +0000</pubDate>
				<category><![CDATA[Шаблоны]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=122</guid>

					<description><![CDATA[<p>Шаблон проектирования &#171;Посетитель&#187; относится к поведенческим шаблонам и позволяет классам реализовывать некий дополнительный, не свойственный им и не относящийся к их основной ответственности функционал, вынося его в отдельные классы-посетители. Описание работы. Предположим, в нашем проекте есть некие сервисные классы, выполняющие определенные, специфические задачи. Появилась необходимость добавить им новый функционал, что бы они могли реализовать не ... <a title="Шаблон &#171;Посетитель (Visitor)&#187;" class="read-more" href="https://mcodex.ru/pattern-visitor/" aria-label="Прочитать больше о Шаблон &#171;Посетитель (Visitor)&#187;">Читать далее</a></p>
<p>Сообщение <a href="https://mcodex.ru/pattern-visitor/">Шаблон &#171;Посетитель (Visitor)&#187;</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Шаблон проектирования &#171;Посетитель&#187; относится к поведенческим шаблонам и позволяет классам реализовывать некий дополнительный, не свойственный им и не относящийся к их основной ответственности функционал, вынося его в отдельные классы-посетители.</p>
</blockquote>



<h2 class="wp-block-heading">Описание работы.</h2>



<p class="wp-block-paragraph">Предположим, в нашем проекте есть некие сервисные классы, выполняющие определенные, специфические задачи. Появилась необходимость добавить им новый функционал, что бы они могли реализовать не свойственную им задачу. Самое простое &#8212; добавить соответствующие методы в каждый класс, однако это не всегда приемлемо, так как нарушает принципы SRP, OCP и другие.</p>



<p class="wp-block-paragraph">Вместо этого создается отдельный новый класс &#8212; <strong>посетитель</strong>, в котором есть реализующие требуемый функционал методы.</p>



<p class="wp-block-paragraph">В сервисный класс добавляется метод (часто имеющий название <strong>accept</strong>), в который принимает объект посетителя как параметр и вызывает у него соответствующий метод.</p>



<p class="wp-block-paragraph">Таким образом, в сервисные классы вносятся минимальные изменения (только принимающий метод), а весь дополнительный функционал реализуется в отдельном классе-посетителе.</p>



<h2 class="wp-block-heading">Пример реализации</h2>



<p class="wp-block-paragraph">Есть CRM, в которой существуют разные типы записей &#8212; account, client, product, supplier и т.д. У каждого типа записи своя структура. Необходимо добавить реализацию экспорта всех записей в файл.</p>



<p class="wp-block-paragraph">Для этого для каждого типа записи создается отдельный класс-посетитель, реализующий интерфейс <strong>Visitor</strong> с методом <strong>export</strong>.<br>В каждый класс записи добавляется метод <strong>accept</strong>, который принимает объект посетителя и вызывает его метод <strong>export</strong>.</p>



<p class="wp-block-paragraph">В клиентском коде для экспорта у каждого экземпляра записи вызывается метод <strong>accept</strong>.</p>



<p class="wp-block-paragraph">В результате всю работу по экспорту выполняет посетитель, а запись лишь предоставляет свои данные и вызывает метод посетителя, не реализуя самостоятельно логику экспорта.</p>



<p class="wp-block-paragraph">В данном примере метод accept можно вынести в базовый класс записей, так как его реализация одинакова для всех. Однако в реальных проектах это подходит не всегда и метод <strong>accept</strong> требуется реализовывать каждом классе.</p>



<h3 class="wp-block-heading">Интрефейсы</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>// интерфейс для записей
interface Entity
{
    public function accept(Visitor $visitor): void;
}

// интерфейс для посетителей
interface Visitor
{
    public function export(Entity $entity): void;
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// интерфейс для записей</span></span>
<span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> Entity</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">accept</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Visitor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">visitor</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// интерфейс для посетителей</span></span>
<span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> Visitor</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">export</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Entity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entity</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Классы записей</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class Product implements Entity
{
    protected string $title;
    protected float $price;

    public function accept(Visitor $visitor): void
    {
        $visitor->export($this);
    }
}


class Account implements Entity
{
    protected int $id;
    protected float $amount;

    public function accept(Visitor $visitor): void
    {
        $visitor->export($this);
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Product</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Entity</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">float</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">price</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">accept</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Visitor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">visitor</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">visitor</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">export</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Account</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Entity</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">float</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">amount</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">accept</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Visitor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">visitor</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">visitor</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">export</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Классы посетителей</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class ProductVisitor implements Visitor
{

    public function export(Entity $entity)
    {
      // логика экспорта
    }
}


class ClientVisitor implements Visitor
{
    public function export(Entity $entity)
    {
      // логика экспорта
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ProductVisitor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Visitor</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">export</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Entity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entity</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">      </span><span style="color: #616E88">// логика экспорта</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ClientVisitor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Visitor</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">export</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Entity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">entity</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">      </span><span style="color: #616E88">// логика экспорта</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Клиентский код</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$product = new Product;
$account = new Account;

// делаем экспорт
$account->accept(new AccountVisitor);
$product->accept(new ProductVisitor);

</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">product</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Product</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">account</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Account</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// делаем экспорт</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">account</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">accept</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AccountVisitor</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">product</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">accept</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ProductVisitor</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Ссылки:</h3>



<p class="wp-block-paragraph"><a href="https://github.com/maximmas/patterns">https://github.com/maximmas/patterns</a></p>



<p class="wp-block-paragraph"><a href="https://refactoring.guru/ru/design-patterns">https://refactoring.guru/ru/design-patterns</a></p>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/pattern-visitor/">Шаблон &#171;Посетитель (Visitor)&#187;</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Соединение таблиц через JOIN в MySQL</title>
		<link>https://mcodex.ru/joins-in-mysql/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Thu, 25 Dec 2025 18:50:39 +0000</pubDate>
				<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=56</guid>

					<description><![CDATA[<p>Соединением называется механизм выборки из нескольких таблиц записей, связанных по определенному условию и является одной из самых распространенных задач при работе с базами данных. Соединение таблиц связано с понятием декомпозиции базы данных, поэтому сначала кратко разберемся, что это такое.Декомпозиция &#8212; подход, при котором данные хранятся не в одной общей таблице, а в нескольких, более специфичных ... <a title="Соединение таблиц через JOIN в MySQL" class="read-more" href="https://mcodex.ru/joins-in-mysql/" aria-label="Прочитать больше о Соединение таблиц через JOIN в MySQL">Читать далее</a></p>
<p>Сообщение <a href="https://mcodex.ru/joins-in-mysql/">Соединение таблиц через JOIN в MySQL</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-code"><code>Дисклеймер: Данная статья является кратким изложением базовых концепций и не претендует на максимальный охват темы. </code></pre>



<p class="wp-block-paragraph">Соединением называется механизм выборки из нескольких таблиц записей, связанных по определенному условию и является одной из самых распространенных задач при работе с базами данных.</p>



<p class="wp-block-paragraph">Соединение таблиц связано с понятием декомпозиции базы данных, поэтому сначала кратко разберемся, что это такое.<br>Декомпозиция &#8212; подход, при котором данные хранятся не в одной общей таблице, а в нескольких, более специфичных и связываются между собой по определенному полю, называемому внешним ключом. Декомпозиция облегчает поддержку и масштабируемость базы данных, но при ее использовании, что бы вывести полные данные записи, расположенные в разных таблицах, необходимо задействовать соединение.</p>



<p class="wp-block-paragraph"><strong>Пример:</strong><br>База данных новостного сайта.<br>В ней хранятся статьи, у каждой статьи есть автор, категория, комментарии.<br>Технически можно хранить все в одной общей таблице, но по мере увеличения количества записей какой-либо из сущностей, такую таблицу очень быстро станет сложно поддерживать.<br><br><strong>Пояснение: </strong><br>Например, что бы переименовать категорию, нужно пройти по всем строкам со статьями из этой категории и в каждой внести изменения, а что то бы просто посмотреть какие есть категории, нужно делать выборку по всей таблице с удалением дублей по колонке категорий. <br><br>Поэтому используется декомпозиция &#8212; каждая сущность (статья, автор, категория, комментарий) хранятся в отдельных таблицах и связываются между собой через внешние ключи.<br>Внешний ключ &#8212; столбец в таблице, в каждой строке которого хранится значение уникального идентификатора (обычно это первичный ключ) из связанной таблицы. <br>Таким образом, внешний ключ &#8212; своего рода указатель на строку другой таблицы, который используется при соединении.</p>



<p class="wp-block-paragraph">Внешний ключ не является обязательным. Можно просто добавить столбец со значениями идентификаторов из связанной таблицы, не указывая, что это внешний ключ.<br>Но если такой столбец назначить внешним ключом, то на таблицу накладывается так называемое &#171;ограничение&#187; (constrains) &#8212; жесткое правило, гарантирующее, что в связанной таблице точно есть нужные данные. Такое ограничение используется для обеспечения &#171;ссылочной целостности (консистентости)&#187; базы данных &#8212; гарантии, что свзязи указывают на существующие данные.</p>



<p class="wp-block-paragraph">Пример таблиц<br><br><strong>Articles</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>id</strong></td><td><strong>title</strong></td><td><strong>text</strong></td><td><strong>author_id</strong></td><td><strong>category_id</strong></td></tr><tr><td>1</td><td>Hello World!</td><td>Lorem Ispum</td><td>1</td><td>1</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>Categories</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>i<strong>d</strong></td><td><strong>name</strong></td></tr><tr><td>1</td><td>Business</td></tr><tr><td>2</td><td>Travel</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Возвращаемся к соединению.<br>Предположим, есть задача получить записи статей, а так же ее автора и категорию.<br>Это можно сделать через операцию соединения таблиц используя оператор JOIN.</p>



<p class="wp-block-paragraph">Предположим, есть задача получить записи статей c названиями категорий.<br>Это можно сделать через операцию соединения таблиц используя оператор JOIN.</p>



<p class="wp-block-paragraph">Формат</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT поля_таблиц
FROM таблица_1
JOIN таблица_2
    ON условие_соединения
JOIN таблица_3
    ON условие_соединения
...    </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> поля_таблиц</span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> таблица_1</span></span>
<span class="line"><span style="color: #81A1C1">JOIN</span><span style="color: #D8DEE9FF"> таблица_2</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> условие_соединения</span></span>
<span class="line"><span style="color: #81A1C1">JOIN</span><span style="color: #D8DEE9FF"> таблица_3</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> условие_соединения</span></span>
<span class="line"><span style="color: #D8DEE9FF">...    </span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Пример простого соединения 2-х таблиц (статьи и категории):</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT articles.id, articles.title, categories.name 
FROM articles
JOIN categories
  ON categories.id = articles.category_id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> articles.id, articles.title, categories.name </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles</span></span>
<span class="line"><span style="color: #81A1C1">JOIN</span><span style="color: #D8DEE9FF"> categories</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> categories.id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> articles.category_id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">В результате будут выведены все заголовки статей и их категории.</p>



<p class="wp-block-paragraph">Если нужны все столбцы из articles:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT articles.*, categories.name 
FROM articles
JOIN categories
  ON categories.id = articles.category_id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> articles.</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF">, categories.name </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles</span></span>
<span class="line"><span style="color: #81A1C1">JOIN</span><span style="color: #D8DEE9FF"> categories</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> categories.id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> articles.category_id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Если нужны все столбцы из обеих таблиц:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT *
FROM articles
JOIN categories
  ON categories.id = articles.category_id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles</span></span>
<span class="line"><span style="color: #81A1C1">JOIN</span><span style="color: #D8DEE9FF"> categories</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> categories.id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> articles.category_id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Соединение может быть внутренним (INNER) или внешним (OUTER)<br>Если тип соединения не указан, то по умолчанию используется внутреннее соединение.<br>Внешнее соединение может быть левое (LEFT) или правое (RIGHT)<br>Для внешнего соединения модификатор OUTER можно не указывать, тип (LEFT/RIGHT) &#8212; обязательно.</p>



<p class="wp-block-paragraph">Примеры указания типов:<br><strong>INNER JOIN </strong>&#8212; явно указано внутренне соединение<br><strong>JOIN </strong>&#8212; тип не указан, то же самое, что INNER JOIN<br><strong>LEFT OUTER JOIN = LEFT JOIN</strong><br><strong>RIGHT OUTER JOIN = RIGHT JOIN</strong></p>



<h2 class="wp-block-heading">Разница между типами соединений.</h2>



<ol class="wp-block-list">
<li>Внутреннее соединение возвращает только те строки, для которых выполняется условие соединения.<br>В примере:  статьи, у которых есть категории. Если в статье указана несуществующая категория &#8212; такая запись не попадет в результат.</li>
</ol>



<p class="wp-block-paragraph">Внутреннее соединение можно релизовать через оператор <strong>WHERE</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT articles.id, articles.title, categories.name 
FROM articles, categories
WHERE articles.category_id = categories.id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> articles.id, articles.title, categories.name </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles, categories</span></span>
<span class="line"><span style="color: #81A1C1">WHERE</span><span style="color: #D8DEE9FF"> articles.category_id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> categories.id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ol start="2" class="wp-block-list">
<li>Внешнее соединение возвращает все строки. Если условие не выполняется (в статье ссылка на несущуствующую категорию) в итогой таблице вместо значений будет подставленно NULL.</li>
</ol>



<p class="wp-block-paragraph"><strong>Варианты внешнего соединения:</strong><br><strong>LEFT JOIN </strong>&#8212; берутся все записи из левой таблицы, из правой &#8212; только совпавшие по условию соединения<br><strong>RIGHT JOIN</strong> &#8212; берутся все записи из правой таблицы, из левой &#8212; только совпавшие по условию соединения</p>



<pre class="wp-block-code"><code>Положение таблицы (левая или правая) определяется ее позицией в запросе. Та что стоит первая - левая.</code></pre>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT * 
FROM левая_таблица l
LEFT JOIN правая_таблица r
ON l.user_id = r.id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> левая_таблица l</span></span>
<span class="line"><span style="color: #81A1C1">LEFT JOIN</span><span style="color: #D8DEE9FF"> правая_таблица r</span></span>
<span class="line"><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> l.user_id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> r.id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph"><strong>Пример LEFT JOIN</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT a.*, c.name 
FROM articles a
LEFT JOIN categories c
ON a.category_id = c.id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> a.</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF">, c.name </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles a</span></span>
<span class="line"><span style="color: #81A1C1">LEFT JOIN</span><span style="color: #D8DEE9FF"> categories c</span></span>
<span class="line"><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> a.category_id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> c.id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">В результате будут выведены все статьи, если у статьи нет категории &#8212; в c.name будет NULL<br>Если у категории нет статьи &#8212; категория не появится в результатах.</p>



<p class="wp-block-paragraph"><strong>Пример RIGHT JOIN</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT a.*, c.name 
FROM articles a
RIGHT JOIN categories c
ON a.category_id = c.id;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> a.</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF">, c.name </span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> articles a</span></span>
<span class="line"><span style="color: #81A1C1">RIGHT JOIN</span><span style="color: #D8DEE9FF"> categories c</span></span>
<span class="line"><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> a.category_id </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> c.id;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">В результате будут выведены все категории, если у категории нет статьи &#8212; в столбцах a.* будет NULL <br>Тот же результат можно получить через LEFT JOIN, поменяв таблицы местами, поэтому на практике RIGHT JOIN редко используется.</p>



<pre class="wp-block-code"><code>Еще раз: LEFT JOIN - выводим все строки из первой таблицы, а из второй те, для которых выполняется условие соединения.</code></pre>



<h2 class="wp-block-heading">Фильтрация результатов с помощью оператора WHERE.</h2>



<p class="wp-block-paragraph">Порядок выполнения таких запросов:<br>FROM<br>JOIN<br>WHERE<br>SELECT</p>



<p class="wp-block-paragraph"><strong>Пример:</strong><br>Необходимо вывести названия и ID всех категорий, в которых нет статей.</p>



<p class="wp-block-paragraph"><strong>Решение</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>SELECT c.id, c.name
FROM categories c
LEFT JOIN articles a
  ON c.id=a.category_id
WHERE a.id IS NULL;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> c.id, c.name</span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> categories c</span></span>
<span class="line"><span style="color: #81A1C1">LEFT JOIN</span><span style="color: #D8DEE9FF"> articles a</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> c.id</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">a.category_id</span></span>
<span class="line"><span style="color: #81A1C1">WHERE</span><span style="color: #D8DEE9FF"> a.id </span><span style="color: #81A1C1">IS</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">NULL</span><span style="color: #D8DEE9FF">;</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Как работает:<br>Шаг 1. FROM .. JOIN ..<br>Создается соединение, в результате получается промежуточная таблица со всеми строками из таблицы категорий. Столбцы &#8212; все из обеих таблиц. Если у категории нет статьи &#8212; в ячейках (id, title, text, …) значение NULL<br>Шаг 2<strong>. </strong>Применяется фильтр WHERE, остаются только строки с aricles.id = NULL<br>Шаг 3<strong>. </strong>Применяется SELECT, остаются только categories.id/name.</p>



<p class="wp-block-paragraph"></p>
<p>Сообщение <a href="https://mcodex.ru/joins-in-mysql/">Соединение таблиц через JOIN в MySQL</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Спред-оператор в JavaScript</title>
		<link>https://mcodex.ru/spread-operator-javascript/</link>
		
		<dc:creator><![CDATA[mcodex]]></dc:creator>
		<pubDate>Wed, 13 Aug 2025 11:29:07 +0000</pubDate>
				<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">https://mcodex.ru/?p=7</guid>

					<description><![CDATA[<p>Язык программирования JavaScript с версией ECMAScript 6 (ES6) получил большое количество новых возможностей и улучшений синтаксиса, которые сделали его еще больше мощным и выразительным. Одним из ключевых нововведений стал спред-оператор, сразу получивший популярность у разработчиков за свою краткость и универсальность.</p>
<p>Сообщение <a href="https://mcodex.ru/spread-operator-javascript/">Спред-оператор в JavaScript</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><br>Язык программирования JavaScript с версией ECMAScript 6 (ES6) получил большое количество новых возможностей и улучшений синтаксиса, которые сделали его еще больше мощным и выразительным. Одним из ключевых нововведений стал <strong>спред-оператор</strong>, сразу получивший популярность у разработчиков за свою краткость и универсальность.</p>



<p class="wp-block-paragraph">В данной статье мы ближе познакомимся с этим оператором, а также узнаем, как с его помощью упростить написание кода.</p>



<h2 class="wp-block-heading">Роль спред-оператора.</h2>



<p class="wp-block-paragraph">Спред-оператор в JavaScript является синтаксической конструкцией, позволяющей передавать элементы итерируемых коллекций (например, массивы, строки или объекты) в другие итерируемые коллекции или функции.<br>Он указывается в виде трех точек “…”, за которыми следует выражение или коллекция. <br>Спред-оператор &#8212; это мощный инструмент, предоставляющий краткий и гибкий способ работы с<br>данными в JavaScript. Его можно использовать для соединения массивов, создания<br>поверхностных копий массивов, преобразования строк в массивы символов, соединения или клонирования объектов, а также для динамической передачи значений в функции или конструкторы, включая множество других способов применения. <br>Благодаря спред-оператору многие операции становятся проще и выразительнее, поэтому он и получил широкую популярность среди JavaScript-разработчиков.</p>



<h2 class="wp-block-heading">Синтаксис и использование спред-оператора в JavaScript</h2>



<p class="wp-block-paragraph">Чтобы продемонстрировать синтаксис спред-оператора, давайте рассмотрим несколько примеров его применения при работе с массивами, строками и объектами.</p>



<h3 class="wp-block-heading">1. Соединение массивов</h3>



<p class="wp-block-paragraph">Можно использовать спред-оператор, чтобы передать элементы одного массива в другой. Это особенно удобно при соединении массивов или создания поверхностной копии массива.</p>



<p class="wp-block-paragraph"><strong>Пример:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const originalArray = [&#91;1, 2, 3, 4&#93;, 12];​
const copiedArray = &#91;...originalArray&#93;;​
​
copiedArray&#91;0&#93;.push(99);​
​
console.log(originalArray); // [&#91;1, 2, 3, 4, 99&#93;, 12]​
console.log(copiedArray); // [&#91;1, 2, 3, 4, 99&#93;, 12]​</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [&#91;</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">12</span><span style="color: #D8DEE9FF">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> &#91;</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">push</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">99</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// [&#91;1, 2, 3, 4, 99&#93;, 12]​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// [&#91;1, 2, 3, 4, 99&#93;, 12]​</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">2. Преобразование строк</h3>



<p class="wp-block-paragraph">Спред-оператор можно использовать для преобразования строки в массив её символов. Это делает работу со строками удобнее: получив массив символов, вы можете применять к нему методы массивов или объединять его с другими массивами.</p>



<p class="wp-block-paragraph"><strong>Пример:</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const str = "Hello";​
​
const charArray = &#91;...str&#93;;​
console.log(charArray); // &#91;'H', 'e', 'l', 'l', 'o'&#93;​</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">str</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Hello</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">charArray</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> &#91;</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">str</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">charArray</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// &#91;&#39;H&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;&#93;​</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">3. Соединение и клонирование объектов</h3>



<p class="wp-block-paragraph">Спред-оператор можно использовать для передачи свойств из одного объекта в другой. Это помогает при соединении или клонировании объектов. Создается новый объект, в котором<br>некоторые свойства могут быть переопределены. Также можно извлекать из объекта нужные свойства.</p>



<p class="wp-block-paragraph"><strong>Пример:​</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const obj1 = { a: 1, b: 2 };​
const obj2 = { b: 3, c: 4 };​
​
// Соединение объектов​
const mergedObj = { ...obj1, ...obj2 };​
console.log(mergedObj); // Output: { a: 1, b: 3, c: 4 }​
​
// Клонирование​
const clonedObj = { ...obj1 };​
console.log(clonedObj); // Output: { a: 1, b: 2 }​</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">obj1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">a</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">b</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">obj2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">b</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">c</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #616E88">// Соединение объектов​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">mergedObj</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">obj1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">obj2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">mergedObj</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// Output: { a: 1, b: 3, c: 4 }​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #616E88">// Клонирование​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">clonedObj</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">obj1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">clonedObj</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// Output: { a: 1, b: 2 }​</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">4. Передача аргументов в функции.</h3>



<p class="wp-block-paragraph">Спред-оператор можно использовать в других ситуациях, например, в аргументах функции, для динамической передачи значений.</p>



<p class="wp-block-paragraph"><strong>Пример:​</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const numbers = &#91;1, 2, 3&#93;;​
const sum = (a, b, c) => a + b + c;​

console.log(sum(...numbers)); // 6​</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numbers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> &#91;</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sum</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">a</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">a</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">b</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">c</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">sum</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">numbers</span><span style="color: #D8DEE9FF">))</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 6​</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">5. Объединение спред-оператора с остаточным параметром</h3>



<p class="wp-block-paragraph">Мощь функционального программирования особенно заметна, когда спред-оператор используется вместе с другими современными возможностями JavaScript — например, с деструктуризацией массивов и объектов. Такой подход позволяет легко извлекать и обрабатывать элементы массивов или свойства объектов, используя краткий и выразительный синтаксис.</p>



<p class="wp-block-paragraph"><strong>Пример:​</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const numbers = &#91;1, 2, 3, 4, 5&#93;;​
const &#91;first, second, ...rest&#93; = numbers;​
​
console.log(first); // 1​
console.log(second); // 2​
console.log(rest); // &#91;3, 4, 5&#93;​</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numbers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> &#91;</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#91;</span><span style="color: #D8DEE9">first</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">second</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">rest</span><span style="color: #ECEFF4">&#93;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">numbers</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">first</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 1​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">second</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 2​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">rest</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// &#91;3, 4, 5&#93;​</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Приведенные выше примеры демонстрируют универсальность и гибкость спред-оператора, делая его мощным инструментом для лаконичной и эффективной обработки и соединения данных.</p>



<h2 class="wp-block-heading">Еще немного о спред-операторе и поверхностном копировании​.</h2>



<p class="wp-block-paragraph">Необходимо отметить, что с помощью спред-оператора можно создавать поверхностные копии массивов и объектов, а также увеличить производительность при создании больших массивов и<br>объектов.​</p>



<p class="wp-block-paragraph"><strong>Пример</strong></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const originalArray = [&#91;1, 2, 3, 4&#93;, 12];​
const copiedArray = &#91;...originalArray&#93;;​

copiedArray&#91;0&#93;.push(99);

​console.log(originalArray); // [&#91;1, 2, 3, 4, 99&#93;, 12]​
console.log(copiedArray); // [&#91;1, 2, 3, 4, 99&#93;, 12]</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [&#91;</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">12</span><span style="color: #D8DEE9FF">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> &#91;</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">​</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF">&#91;</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">&#93;</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">push</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">99</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">​</span><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">originalArray</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// [&#91;1, 2, 3, 4, 99&#93;, 12]​</span></span>
<span class="line"><span style="color: #D8DEE9">console</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">log</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">copiedArray</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// [&#91;1, 2, 3, 4, 99&#93;, 12]</span></span></code></pre></div>



<p class="wp-block-paragraph">В этом коде <strong>originalArray</strong> состоит из двух элементов. С помощью спред-оператора мы создаем новый массив <strong>copiedArray</strong> и переносим в него элементы исходного массива. Затем мы изменяем первый элемент <strong>copiedArray</strong>, добавив число 99 с помощью метода push().</p>



<p class="wp-block-paragraph">После вывода <strong>copiedArray</strong> мы видим, что к первому элементу массива было добавлено 99, но есть проблема с поверхностным копированием, которое делает спред-оператор. Изменения в <strong>copiedArray</strong> оказывают влияние на <strong>originalArray</strong>.</p>



<p class="wp-block-paragraph">Это происходит потому, что спред-оператор не создает абсолютно новые копии элементов или свойств, а скорее делится ссылками на исходные элементы или свойства. Это может оказывать<br>влияние на производительность при работе с большими массивами или объектами.</p>



<p class="wp-block-paragraph">Поэтому при работе с большими массивами или объектами или при необходимости внесения каких-то серьезных изменений в скопированный массив, при этом не затрагивая исходный массив, можно рассмотреть другие способы, такие как глубокое копирование или использование специальных библиотек для обработки сложных структур данных.</p>



<p class="wp-block-paragraph">Необходимо разумно пользоваться спред-оператором и принимать во внимание наилучшие практики для достижения оптимальной производительности кода и удобства его поддержки.</p>



<h2 class="wp-block-heading">3 полезных совета для оптимизации производительности и избежания распространенных ошибок.</h2>



<p class="wp-block-paragraph">Для оптимизации производительности и избежания распространенных ошибок при работе со спред-оператором используйте следующие рекомендации:</p>



<p class="wp-block-paragraph">1.​ Не используйте спред-оператор для передачи элементов больших массивов или объектов в критичных для производительности участках кода.</p>



<p class="wp-block-paragraph">2. Помните о возможных побочных эффектах при передаче вложенных объектов. При  необходимости рассмотрите возможность использования методов глубокого<br>клонирования.</p>



<p class="wp-block-paragraph">3. Пользуйтесь спред-оператором разумно и, если производительность имеет значение, рассмотрите альтернативные подходы.</p>



<h2 class="wp-block-heading">Резюме</h2>



<p class="wp-block-paragraph">Спред-оператор позволяет выполнять лаконичное и чистое соединение массивов, клонирование массивов и объектов, слияние объектов, динамическое создание аргументов функции, клонирование сложных вложенных объектов и массивов и многое другое.</p>



<p class="wp-block-paragraph">Благодаря своей гибкости спред-оператор играет важную роль в развитии JavaScript,  предоставляя разработчикам возможность писать более лаконичный, читабельный и эффективный код.</p>



<p class="wp-block-paragraph">Оригинал статьи:</p>



<p class="wp-block-paragraph"><a href="https://kinsta.com/knowledgebase/spread-operator-javascript">https://kinsta.com/knowledgebase/spread-operator-javascript</a></p>
<p>Сообщение <a href="https://mcodex.ru/spread-operator-javascript/">Спред-оператор в JavaScript</a> появились сначала на <a href="https://mcodex.ru">mcodex</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
