From repository to cloud: client preview and deploy with Harbor
The whole loop around a Maho project: create the repository, develop locally with Harbor, hand the client an ephemeral preview link, push to main and publish with the deploy button in the MageMEGA cloud panel.
A store is only as good as the path the code takes to reach it. Between an empty repository and the day the client clicks a live URL there are four stops: the repository, your machine, a preview link and the production server. Harbor, the Docker Compose launcher for Maho, covers the local stop; a Cloudflare tunnel turns your laptop into a URL you can hand over; MageMEGA takes care of the last mile with a deploy button. This tutorial walks the whole loop on Linux, macOS and Windows with WSL, starting from an empty repository. It does not matter what the store sells, only that a change travels from the editor to a public page without surprises. The whole workflow fits in four commands:
git push origin main # store the change
./vendor/bin/harbor tunnel quick --autoconfig # show it to the client
# ... then press Deploy in the MageMEGA panel # publish it
⚓ Ship’s log. Every departure needs a port that knows the ship, a dock that holds it and a lighthouse the client can see from the shore. Here the repository is the port, Harbor is the dock and the tunnel is the lighthouse.
Requirements
The examples use Harbor, MageMEGA’s open source Docker Compose launcher, inspired by Laravel Sail.
On the host you need:
- Docker with the Compose v2 plugin: Docker Desktop on macOS and Windows, Docker Desktop or Docker
Engine on Linux. Harbor prefers
docker composeand falls back todocker-compose. - PHP >= 8.3, only to run Composer, to create the project and add Harbor. Everything else runs inside the container, which ships PHP 8.4 by default.
- Git, with an account on GitHub, GitLab or Bitbucket.
- A MageMEGA account for the last step (optional if the local preview is all you need).
Windows: work inside WSL
On Windows, run every command in this tutorial inside a WSL2 distribution (Ubuntu is the usual choice):
- Install Docker Desktop with the WSL2 backend, or Docker Engine inside the distribution.
- Install Git and PHP/Composer in the distribution, not only on the Windows side.
- Keep the project in the Linux filesystem, for example
~/projects/my-store. Do not work under/mnt/c: file watching is slow there and the containers see a different mount. - Open
https://localhost:8443from the Windows browser as usual: WSL2 forwardslocalhost.
Quick start: from an empty repository to a public page
1. Create the repository
Start on the hosting side, not on your laptop. Create an empty repository on GitHub (GitLab and
Bitbucket work the same way): no README, no .gitignore, no license, so the first push has nothing
to reconcile. Name it after the project, for example my-store, and keep it private while it is a
client project.
Copy the SSH URL, it looks like [email protected]:<owner>/my-store.git. MageMEGA clones over SSH
only, so we stay on SSH from the start. To push yourself over SSH you also need your own key added
to the Git host: the deploy key MageMEGA gives you is a separate, server-side key, added in step 6.
2. Create the Maho project and add Harbor
composer create-project mahocommerce/maho-starter my-store
cd my-store
composer require --dev empiricompany/harbor # or :dev-main for the latest version
3. Start the local stack
harbor init generates the .harbor/ directory with the project files (.env, compose.yaml,
docker.override.yaml, docker.install.yaml). Then build and start the stack: the first build
takes 5 to 20 minutes, the perfect moment to grab a coffee rather than press Ctrl+C.
./vendor/bin/harbor init
./vendor/bin/harbor up -d --build
./vendor/bin/harbor ps
Install the PHP dependencies and Maho inside the container:
./vendor/bin/harbor composer install
./vendor/bin/harbor maho install --license_agreement_accepted yes \
--locale en_US --timezone UTC --default_currency USD \
--db_host db --db_name maho --db_user maho --db_pass maho \
--url 'https://localhost:8443/' --use_secure 1 --secure_base_url 'https://localhost:8443/' \
--use_secure_admin 1 --admin_username admin --admin_password 'change-me-strong' \
--sample_data 1
The store is now on https://localhost:8443/. From here you develop normally: edit the files,
then run harbor maho cache:flush (and harbor maho migrate after installing a module). The full
Harbor workflow, from the CLI to optional profiles, is in Local Maho development with
Harbor; here we only use it as the engine of the
loop.
4. Connect the repository and push the first commit
composer create-project writes the project but does not create a Git repository, so initialize it
and point it at the remote:
git init
git branch -M main
git remote add origin [email protected]:<owner>/my-store.git
git add -A
git commit -m "Initial Maho project with Harbor"
git push -u origin main
Run git status before the first commit and make sure that vendor/ and the generated .harbor/
files are not staged. harbor init writes .harbor/.gitignore, which excludes the generated
compose.yaml and, above all, .harbor/.env: that file holds the tunnel token and the database
password and must never be pushed.
5. Hand the client a preview link
A tunnel exposes the local store on a public HTTPS URL without opening any port on your router. The quick tunnel needs no Cloudflare account:
./vendor/bin/harbor tunnel quick --autoconfig
Harbor starts the tunnel and prints a URL like https://random-words-here.trycloudflare.com.
--autoconfig points Maho’s base URLs at it, disables the base-URL auto-redirect, then reindexes
and flushes the cache; without the flag Harbor only prints the commands to run. Copy the URL, send
it to the client and let them click around.
Emails sent from the preview are still captured by Mailpit on your machine, so the client will not receive real messages: useful for testing order confirmations without spamming anyone. Read them at http://localhost:8025.
The quick URL is ephemeral: it disappears when the tunnel stops. For a demo you repeat, use a named tunnel with a fixed hostname instead:
./vendor/bin/harbor tunnel up --autoconfig
tunnel up reads HARBOR_CLOUDFLARED_TOKEN and HARBOR_CLOUDFLARED_HOSTNAME from .harbor/.env.
When the client is done, close the gangway and put Maho back on localhost:
./vendor/bin/harbor tunnel down --autoconfig
⚓ Ship’s log. The tunnel is a gangway lowered to the dock, not a new pier: the store is still on your laptop, it is reachable from the shore only while the gangway is down.
6. Configure MageMEGA once, then deploy with a button
Open the site in the MageMEGA cloud panel and connect it to the repository. The panel is part of the MageMEGA hosting: if you do not know it, the overview is on the managed MahoCommerce hosting page. This is a one-time job.
- Go to Source → Deploy Key and copy the public key.
- Add it to the Git repository as a deploy key, so the server may clone the code:
- GitHub: Settings → Deploy keys → Add deploy key. Tick “Allow write access” only if the server must push.
- GitLab: Settings → Repository → Deploy keys.
- Bitbucket: Settings → Access keys.
- Go to Source → Repository and fill in:
- Repository URL: the SSH URL,
[email protected]:<owner>/my-store.git. The panel accepts SSH URLs only, because the deploy key cannot authenticate over HTTPS. - Branch:
main.
- Repository URL: the SSH URL,
Before saving, the panel reminds you of two things: the deploy key must already be added to the repository, and the branch must already exist on the remote. Both are true after step 4. Click Save & Deploy: the server fetches the code and runs the first deployment. The Deployment Log tab shows what happened.
7. The daily cycle
From now on every change follows the same short path: develop, preview, push, deploy.
# 1. edit the project, then clear the cache
./vendor/bin/harbor maho cache:flush
# 2. see it on the preview URL while you work
./vendor/bin/harbor tunnel quick --autoconfig
# 3. store the change and send it to the server
git add -A
git commit -m "Add the new homepage banner"
git push origin main
Then open the site card in the MageMEGA panel and press Deploy. The server fetches main and
redeploys; the progress and the result are in Deployment Log. Stop the preview tunnel when you
no longer need it:
./vendor/bin/harbor tunnel down --autoconfig
The loop at a glance
repository → harbor up -d → harbor tunnel quick → git push origin main → Deploy
holds runs locally shows the client stores the change publishes
Four tools, one job each: the repository stores, Harbor launches, the tunnel shows, MageMEGA publishes. The client gets a link, the code stays in one place and the button does the rest.
⚓ Last ship’s log. The port is marked on the chart, the dock is ready and the lighthouse is lit. All that is left is to hoist the sail and press Deploy. Fair winds.
Troubleshooting
./vendor/bin/harbor doctor # validate Docker, Compose and the stack
./vendor/bin/harbor ps # see what is running
./vendor/bin/harbor logs app # PHP errors and Caddy access log
./vendor/bin/harbor config # show the effective Compose configuration
- The tunnel URL does not open. Wait a few seconds after
tunnel quick: cloudflared needs to register with the edge. Checkharbor logs cloudflared-quick. - Maho links point to localhost after a tunnel. You forgot
--autoconfig, or you rantunnel downwithout it and the base URLs stayed on the public host. - The MageMEGA deployment fails on clone. The deploy key is missing on the repository, or the
Repository URL uses
https://instead of the SSH form.
Editor's note.Text prepared with the help of AI and checked against the source before publication. It can still contain mistakes: if you find one, report it.Read the manifesto
Does your store still sell?
Tell us where you are starting from: we reply with a first assessment, no strings attached.
Ask for the store assessment