Installation
System Installation
This guide follows the real deployment flow used in the deployment folder: source code is stored on GitHub, GitHub Actions packages the release, and the server receives it over SSH. This approach is useful when you want deployment to happen after a normal push instead of a manual upload every time.
Understand these 6 ideas first
GitHub repository: the place that stores your project source codeGitHub Actions: the automation that builds and deploys after code is pushedserver: the machine that runs the real website.env: backend configuration such as domain, database, mail, cache, and queue.env.admin: frontend configuration for the admin panelSSH key: the credential pair that lets GitHub connect to the server securely without typing a password
What does this installation flow look like?
- You push code to the
devormainbranch. GitHub Actionsruns theCI/CDworkflow.- The workflow creates a compressed artifact with the code and
.github/deployment. - GitHub uploads that artifact to the server over
SSH. - The server creates a new release folder, links
storage,.env, and.env.admin, then runs the install steps. - When everything succeeds, the
currentsymlink moves to the new release and the server keeps the five newest releases.
Step 1: Prepare what you need
Before you begin, make sure you have:
- access to the GitHub repository
- permission to create repository
SecretsandVariables - SSH access to the server
- the website domain and the admin domain
- database details
- mail, Redis, and file storage details if the project uses them
On the server, check these tools first:
PHPin the version expected by the workflow, currently8.3ComposerNode.jsandpnpm- the
aclpackage sosetfaclis available - a web server such as
nginxorapache - a background process manager such as
supervisor
Important detail
In the current deployment scripts, pnpm build steps run directly on the server. That applies to the main frontend build and also to admin when an admin directory exists. The server therefore needs working Node.js and pnpm.
Step 2: Upload the project to GitHub
If the project does not have a repository yet:
- Create a new repository on GitHub.
- Upload the project source code into that repository.
- Create at least two working branches, usually
devfor staging andmainfor production.
If the repository already exists, verify:
devis the branch used for testing or stagingmainis the branch used for production- the
.githubfolder is present in source control
Step 3: Copy the workflow and deployment scripts into the project
The current project already includes working samples under deployment/build-script. Copy them into the application repository:
- Copy
deployment/build-script/workflows/ci-cd.ymlinto.github/workflows/ci-cd.yml. - Copy the full
deployment/build-script/deploymentfolder into.github/deployment/.
The minimum structure should look like this:
.github/
workflows/
ci-cd.yml
deployment/
prepare.sh
deploy.sh
hooks/
before-activation.sh
after-activation.sh
flush-opcache.sh
set-file-permissions.sh
Step 4: Review the workflow before using it
The sample workflow runs on pushes to dev and main. Review these points before the first deployment:
PHP_VERSIONis currently8.3- the sample
deploy-devjob still contains the placeholder/var/www/[domain_folder] - the sample
deploy-mainjob readsDOMAIN_FOLDER
In practice, this means you should update the workflow first:
- replace the
deploy-devplaceholder with a real path - or standardize both environments to use repository variables
Simple example:
- if the project lives in
/var/www/my-project - then
base_directorymust be/var/www/my-project - do not point it to
/var/www/my-project/current
prepare.sh checks this and will stop if you point to the current folder directly.
Step 5: Create a deploy user on the server
Use a separate account for automated deployment when possible, for example deploy or githubconnector. Avoid using root unless you absolutely need it.
That user should:
- have access to the project folder
- be able to run
php,composer, andpnpm - belong to the same group as the web server user, commonly
www-dataornginx
Example:
sudo usermod -aG www-data deploy
Step 6: Create an SSH key for GitHub deployment
You need a key pair:
- the
private keygoes into GitHub Secrets - the
public keygoes into~/.ssh/authorized_keysfor the deploy user on the server
Example command:
ssh-keygen -t ed25519 -C "github-actions-deploy"
Then:
- copy the full private key content into the GitHub secret
SSH_PRIVATE_KEY - add the public key to the deploy user’s
authorized_keysfile on the server
Common mistake
Do not paste the public key into SSH_PRIVATE_KEY. The deployment preparation script checks for this and will stop if the value looks like a public key.
Step 7: Generate known hosts
known hosts tells GitHub which server identity it should trust during SSH.
Generate it like this:
ssh-keyscan -p 22 -H your-server.com
If the server uses another port, replace 22 with the real one. Store the full output in the GitHub secret SSH_KNOWN_HOSTS.
Step 8: Add GitHub Secrets and Variables
In the repository, go to Settings -> Secrets and variables -> Actions.
Create these Secrets:
REMOTE_USERREMOTE_HOSTREMOTE_PORTSSH_PRIVATE_KEYSSH_KNOWN_HOSTS
Create these Variables if you follow the sample workflow:
DOMAIN_FOLDER
That variable is used to build the base path, for example:
/var/www/${DOMAIN_FOLDER}
Step 9: Prepare the base directory on the server
With the current release-based deployment flow, each project should have one base directory, for example:
/var/www/my-project
Create the basic structure first:
sudo mkdir -p /var/www/my-project/config
sudo mkdir -p /var/www/my-project/storage
sudo chown -R deploy:www-data /var/www/my-project
sudo chmod -R 2775 /var/www/my-project
After the first deployment, the structure will look more like this:
/var/www/my-project
.env
.env.admin
config/
setting.php
modules_statuses.json
storage/
releases/
1/
2/
current -> /var/www/my-project/releases/2
If you are migrating from an older layout that uses shared/storage and shared/.env, the deployment script can detect that and continue using it.
Step 10: Create .env on the server
The .env file must exist in the base directory and must not be empty. If it is empty, deploy.sh will stop.
Example location:
/var/www/my-project/.env
At minimum, you usually need to fill in:
- application settings:
APP_NAME,APP_ENV,APP_KEY,APP_URL,APP_DEBUG - domain and session settings:
APP_DOMAIN,ASSET_URL,SESSION_DOMAIN,SANCTUM_STATEFUL_DOMAINS - locale and timezone:
DEFAULT_LOCALE,TIMEZONE - database:
DB_CONNECTION,DB_HOST,DB_PORT,DB_DATABASE,DB_USERNAME,DB_PASSWORD - cache, queue, and session:
CACHE_DRIVER,QUEUE_CONNECTION,SESSION_DRIVER,REDIS_* - internal auth keys:
ISPA_API_AUTH_KEY,ISPA_ADMIN_AUTH_KEY - mail:
MAIL_*if the project sends mail - file storage:
AWS_*if the project uses object storage
The variables that most often affect sign-in are:
APP_URLSESSION_DOMAINSANCTUM_STATEFUL_DOMAINSISPA_ADMIN_AUTH_KEY
If admin runs on a separate subdomain, these values must match the real domains exactly. Wrong values here often cause cookie issues, login failures, or broken API calls.
Step 11: Create .env.admin on the server
This file is for the admin frontend and must also not be empty.
Example location:
/var/www/my-project/.env.admin
The most common variables are:
VITE_DOMAIN: the admin domainVITE_BASE_URL: the backend API URL used by adminVITE_PROTOCOL: usuallyhttpsVITE_NODE_ENV: the runtime environmentVITE_DEFAULT_LOCALE: the default admin languageVITE_APP_ADMIN_AUTH_KEY: the admin auth key used by frontendVITE_SECURE_COOKIE: should be enabled under HTTPS
Two variables are especially important:
VITE_BUILD_VERSION: the deployment script updates this automatically during buildVITE_APP_ADMIN_AUTH_KEY: it must stay compatible with the backend admin auth key
Step 12: Prepare config/setting.php
If the project uses config/setting.php for persistent project settings, place it here:
/var/www/my-project/config/setting.php
If it does not exist during the first deployment:
- the script tries to reuse it from the previous release if available
- otherwise it copies the file from the current release artifact
This is how the system keeps that file stable across deployments.
Step 13: Configure file and directory permissions
This part matters if you want uploads, logs, cache, and queue workers to run without write errors.
According to deployment/build-script/deployment/hooks/set-file-permissions.sh, the system expects:
- the deploy user and web server user to share a group
setfaclto be installed- writable directories to use
0775
Useful checks:
sudo apt install acl
id deploy
id www-data
If the deploy user is not in the web server group:
sudo usermod -aG www-data deploy
The hook also checks config/filesystems.php. The local and public disks should use 0775 for public directories. If they do not, the deployment can stop to prevent future upload failures.
Step 14: Configure the web server
Your domain should point to the public directory of the active release:
/var/www/my-project/current/public
The deployment folder already contains several .conf examples for nginx, including patterns for:
- the main website domain
- admin or frontend domains
- serving files from
/storage - HTTPS configuration
The main rule is simple: always point the PHP site to current/public, not to a specific release folder.
Step 15: Configure queue and schedule workers
For stable CMS operation, background workers are usually required. The deployment folder already includes example queue-*.conf and schedule-*.conf files for supervisor.
What each one does:
queue: processes background jobs such as email, sync, and other heavy tasksschedule: runs recurring tasks
In the examples:
- queue uses
artisan queue:work - schedule uses
artisan schedule:work
After copying the config into supervisor, you normally run:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status
If the project uses a dedicated queue such as gpt_caller, it should have its own worker just like the sample files.
Step 16: Push code and run the first deployment
When the setup above is ready:
- commit
.github/workflows/ci-cd.ymland.github/deployment/* - push to
devormain - open the
Actionstab on GitHub and watch the workflow
If everything is configured correctly, the server will automatically:
- upload the artifact
- create a new release directory
- link
storage,.env,.env.admin, andsetting.php - install Composer dependencies
- run migrations
- run module migrations
- sync CMS permissions and translations
- build admin with
pnpm - switch
currentto the new release - flush
OPCache - keep only the five newest releases
A detail that can look unusual
After the admin build finishes, the current script deletes the admin directory from the release. That is normal for this deployment setup and does not automatically mean the deployment failed.
Step 17: Check the system after deployment
After the first successful run, verify:
- the public website opens correctly
- the admin panel opens and allows sign-in
- you can create a record or upload a file
- the main dashboard and key modules work
storage/logsdoes not show new critical errorssupervisorctl statusshows queue and schedule processes running
Common problems and quick explanations
GitHub cannot connect to the server
The most common causes are:
- wrong
REMOTE_HOSTorREMOTE_PORT - wrong
SSH_PRIVATE_KEY - wrong or missing
SSH_KNOWN_HOSTS
The workflow says .env or .env.admin is empty
The deployment script stops if these files exist but contain no real values. Fill them in on the server and run the deployment again.
The workflow cannot find Composer
Composer is not installed on the server, or the deploy user cannot access it in the shell environment.
The workflow fails on file permissions
The usual reasons are:
aclis not installed- the deploy user is not in the same group as the web server user
- the project directory or
storageis not writable enough
The workflow cannot connect to the database
The before-activation.sh hook checks database connectivity before running migrations. Review DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, and DB_PASSWORD.
The workflow fails while flushing OPCache
That step uses APP_URL. If APP_URL is wrong, points to the wrong protocol, or does not resolve to the active site, the OPCache flush can fail.
Helpful handoff checklist for non-technical owners
When handing the system over, keep a short checklist:
- where the GitHub repository lives
- who can view
Actions - which user is used for SSH deployment
- where
.envlives on the server - where
.env.adminlives on the server - how to check whether queue and schedule are running
- how to identify the latest release under
releases
That way, even someone who does not work with code every day can still follow the right places to inspect the system.

