Apps and clients
New client instance
Every client gets their own instance: their own backend, database, keys and accounts, and their own app, published from their own store accounts. This page is the repeatable path to stand one up.
Tenancy model
Sharkhex Delivery is single-tenant: one deployment serves one client. There is no tenant or organisation layer in the data; everything in a database belongs to that one platform.
- Isolation: customers, orders, payments and payouts of different businesses never share a database, and each instance has its own secrets, JWT keys and storage.
- Own accounts: Stripe, the email provider, Pusher, Google Maps, Expo and the app stores are the client's own accounts, configured in that instance's settings. App stores require white-label apps to be published from the client's own developer accounts.
- Simple updates: an instance is updated by deploying a newer release and running its migrations; instances never affect each other.
What is per instance: the backend .env.local (URL, database, secrets, brand name), the JWT key pair, the database (settings, legal documents, users, orders), uploaded files and private documents, and the app's brand manifest. What is shared: only the codebase release.
1. Accounts the client owns
- A domain with HTTPS, and a server or hosting plan meeting the backend requirements.
- Stripe (payments, and Connect if couriers or stores are paid through Stripe).
- Mailgun (transactional email), Pusher (realtime), Google Maps API keys.
- Expo account and an EAS project; Apple Developer (organisation) and Google Play Console accounts.
- Optional: an S3 bucket for uploads and private documents.
- Optional: Sentry (crash and error reports for the app and the backend) and Firebase (usage analytics, collected only with the user's consent).
2. Backend
With Docker, follow Docker deployment for steps 1 to 4 and 8, then continue at step 5. Without Docker:
-
Check out the release and install dependencies:
composer install --no-dev --optimize-autoloader -
Prepare the instance. This writes
.env.localwith a newAPP_SECRETand JWT passphrase, production settings, the public URL (used for links in emails and cron jobs), the database URL and the brand name, then generates the JWT key pair. It refuses to run on a checkout that is already an instance, so it can never rotate a live instance's keys.php bin/new-instance --url=https://delivery.example.com \ --database-url="mysql://user:password@127.0.0.1:3306/delivery?serverVersion=8.0&charset=utf8mb4" \ --brand="Example Delivery" --from-email=noreply@example.com -
Create the database tables:
php bin/console doctrine:database:create --if-not-exists php bin/console app:database:install -
Point the web server's document root at
public/and openhttps://your-domain/installto create the admin account and enter the platform details. -
In the admin, fill in Settings: General (legal details, public URL, currency, timezone), Payments (Stripe keys; webhook
/api/v1/webhook/stripe), Notifications (Mailgun), Storage, Integrations (Pusher, Maps), Commission, Tax, Delivery and Courier onboarding. -
Review and publish the legal documents (Legal documents → Create drafts from templates).
-
Draw the delivery zones (Locations → Delivery zones). Until the first zone is active, the platform delivers everywhere: see Platform configuration.
-
For push notifications, put the client's Expo access token in
EXPO_DSNin.env.local:expo://ACCESS_TOKEN@default. -
Add the scheduled jobs to cron.
-
Optional: error reports. Put the DSN of the client's Sentry project for the backend in
SENTRY_DSNin.env.local. Releases are named after theVERSIONfile; request bodies, cookies and IP addresses are never sent, and client errors such as 404s are left out. -
Point an uptime monitor at
https://your-domain/api/v1/health(public, answers 200).
3. App
-
Create the client's brand from their logo (the main colour is suggested from it, or pass
--primary):npm run brand:new -- example-delivery --name "Example Delivery" \ --bundle-id com.example.delivery --api-url https://delivery.example.com/api \ --support-email help@example.com --logo ~/Downloads/example-logo.png -
Check the suggested colour (
npm run brand:suggest -- example-delivery) and the generated icons ingenerated/brand/assets/after applying; create the EAS project and put its id inidentity.easProjectId. -
Pick the typeface (
typography.family, or the client's own files) and the languages the app offers (locales.enabledandlocales.default); see Typeface and languages in Mobile app setup. -
Set the build environment:
EXPO_PUBLIC_PUSHER_KEY,EXPO_PUBLIC_PUSHER_CLUSTER,EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY,EXPO_PUBLIC_GOOGLE_MAPS_API_KEY,EXPO_PUBLIC_GOOGLE_MAPS_API_KEY_ANDROID. -
Optional: add the client's Sentry project and Firebase config files to the brand (see Crash reports and Usage analytics in Mobile app setup).
-
Apply and check the brand, then build:
npm run brand:apply -- example-delivery,npm run brand:check, then build with EAS (see Building and publishing).
Currency, country, default language, support email and the legal pages come from the backend at runtime, so they are set in the instance's admin, not in the app.
4. Store submission
Follow the store section of the Deployment checklist: privacy policy URL https://your-domain/legal/privacy, account deletion URL https://your-domain/account/delete.
Create the reviewers' demo accounts (a customer, an approved courier, a live store with products) and add their emails to Settings → Sign-in security → App review accounts. With the portal connected (Build your apps), its launch readiness checks confirm all of this before submission.
5. Updating an instance
- Deploy the newer release, run
composer install --no-dev --optimize-autoloader, thenphp bin/console doctrine:migrations:migrate(or use Admin → Platform Updates). - For the app, apply the same brand on the new release and rebuild. A fix that only changes JavaScript can go out as an over-the-air update instead (see Over-the-air updates).
Found a mistake? Tell us.