deploy: split into dev/prod with separate Shopify configs and containers
- shopify.app.toml -> shopify.app.dev.toml (domain: invoice-app-dev.linumiq.com)
- New shopify.app.prod.toml will be created via shopify app config link --config prod
- docker-compose split into deploy/docker-compose.{dev,prod}.yml with distinct
container names (linumiq-invoice-{dev,prod}), images, env files and bind mounts
- Caddyfile snippet maps both subdomains to their respective containers
- .env.{dev,prod}.example templates committed in deploy/
- deploy/README.md documents the layout and day-to-day workflow
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# DEV environment for linumiq-invoice (custom app installed on linumiq-dev.myshopify.com).
|
||||
# Copy to `.env.dev` on the server (in /docker/linumiq-invoice/dev/) and fill in real values.
|
||||
# NEVER commit the real file.
|
||||
|
||||
# --- Shopify app credentials ---
|
||||
# Partner Dashboard → Apps → linumiq-invoice-dev → API credentials.
|
||||
SHOPIFY_API_KEY=fbc263e6cc28e8de031878d2a0f17444
|
||||
SHOPIFY_API_SECRET=REPLACE_ME
|
||||
|
||||
# Public URL Shopify uses for OAuth, webhooks and admin embedding. Must match shopify.app.dev.toml.
|
||||
SHOPIFY_APP_URL=https://invoice-app-dev.linumiq.com
|
||||
|
||||
# Must match `scopes` in shopify.app.dev.toml.
|
||||
SCOPES=read_orders,write_orders,read_all_orders,read_customers,read_companies,read_files,write_files
|
||||
|
||||
# --- Runtime ---
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
# DATABASE_URL is set in docker-compose.dev.yml (file:/data/prod.sqlite on the bind mount).
|
||||
@@ -0,0 +1,20 @@
|
||||
# PROD environment for linumiq-invoice (custom app installed on shop.linumiq.com / 5aiizq-ti.myshopify.com).
|
||||
# Copy to `.env.prod` on the server (in /docker/linumiq-invoice/prod/) and fill in real values.
|
||||
# NEVER commit the real file.
|
||||
|
||||
# --- Shopify app credentials ---
|
||||
# Partner Dashboard → Apps → linumiq-invoice (prod) → API credentials.
|
||||
SHOPIFY_API_KEY=REPLACE_ME
|
||||
SHOPIFY_API_SECRET=REPLACE_ME
|
||||
|
||||
# Public URL Shopify uses for OAuth, webhooks and admin embedding. Must match shopify.app.prod.toml.
|
||||
SHOPIFY_APP_URL=https://invoice-app.linumiq.com
|
||||
|
||||
# Must match `scopes` in shopify.app.prod.toml.
|
||||
SCOPES=read_orders,write_orders,read_all_orders,read_customers,read_companies,read_files,write_files
|
||||
|
||||
# --- Runtime ---
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
# DATABASE_URL is set in docker-compose.prod.yml (file:/data/prod.sqlite on the bind mount).
|
||||
@@ -1,10 +1,18 @@
|
||||
# Append to your existing Caddyfile (or include via `import`).
|
||||
# DNS A/AAAA record for invoice-app.linumiq.com must point to this server first,
|
||||
# otherwise Caddy will fail to obtain a Let's Encrypt certificate.
|
||||
# DNS A/AAAA records for both subdomains must point to this server first
|
||||
# (a wildcard *.linumiq.com record is sufficient).
|
||||
#
|
||||
# Caddy runs in Docker on the `caddy_net` network and reaches each app by
|
||||
# container name (the apps do not publish host ports).
|
||||
|
||||
# Caddy runs in Docker on the `caddy_net` network and reaches the app by
|
||||
# container name (the app does not publish a host port).
|
||||
# DEV — installed on linumiq-dev.myshopify.com
|
||||
invoice-app-dev.linumiq.com {
|
||||
encode zstd gzip
|
||||
reverse_proxy linumiq-invoice-dev:3000
|
||||
}
|
||||
|
||||
# PROD — installed on shop.linumiq.com (5aiizq-ti.myshopify.com)
|
||||
invoice-app.linumiq.com {
|
||||
encode zstd gzip
|
||||
reverse_proxy linumiq-invoice:3000
|
||||
reverse_proxy linumiq-invoice-prod:3000
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Deployment
|
||||
|
||||
Two independent deployments share the same codebase and Docker image build:
|
||||
|
||||
| env | container | backend domain | install target | partner-dashboard app | shopify config |
|
||||
| ---- | --------------------- | ------------------------------- | ------------------------------------------------ | ----------------------- | ------------------------ |
|
||||
| dev | `linumiq-invoice-dev` | `invoice-app-dev.linumiq.com` | `linumiq-dev.myshopify.com` | `linumiq-invoice-dev` | `shopify.app.dev.toml` |
|
||||
| prod | `linumiq-invoice-prod`| `invoice-app.linumiq.com` | `5aiizq-ti.myshopify.com` (= `shop.linumiq.com`) | `linumiq-invoice` (prod)| `shopify.app.prod.toml` |
|
||||
|
||||
## Server layout (root server)
|
||||
|
||||
```
|
||||
/docker/linumiq-invoice/
|
||||
├── git/ # checkout of this repo (git pull here)
|
||||
├── dev/
|
||||
│ ├── docker-compose.yml # symlink → ../git/deploy/docker-compose.dev.yml
|
||||
│ ├── .env.dev # secrets (NOT in git)
|
||||
│ └── data/ # bind-mounted SQLite + cached assets
|
||||
└── prod/
|
||||
├── docker-compose.yml # symlink → ../git/deploy/docker-compose.prod.yml
|
||||
├── .env.prod # secrets (NOT in git)
|
||||
└── data/ # bind-mounted SQLite + cached assets
|
||||
```
|
||||
|
||||
Both containers attach to the external `caddy_net` Docker network. Caddy reverse-proxies each subdomain to the correct container by name (see `Caddyfile.snippet`).
|
||||
|
||||
## First-time setup on the server
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /docker/linumiq-invoice/{git,dev/data,prod/data}
|
||||
sudo chown -R "$USER" /docker/linumiq-invoice
|
||||
|
||||
cd /docker/linumiq-invoice
|
||||
git clone git@git.linumiq.com:LinumIQ/linumiq-invoice.git git
|
||||
|
||||
# DEV
|
||||
cd /docker/linumiq-invoice/dev
|
||||
ln -s ../git/deploy/docker-compose.dev.yml docker-compose.yml
|
||||
cp ../git/deploy/.env.dev.example .env.dev # then edit secrets
|
||||
docker compose up -d --build
|
||||
|
||||
# PROD
|
||||
cd /docker/linumiq-invoice/prod
|
||||
ln -s ../git/deploy/docker-compose.prod.yml docker-compose.yml
|
||||
cp ../git/deploy/.env.prod.example .env.prod # then edit secrets
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Append `Caddyfile.snippet` to your Caddy config and `docker exec caddy caddy reload --config /etc/caddy/Caddyfile`.
|
||||
|
||||
## Day-to-day redeploy
|
||||
|
||||
```bash
|
||||
cd /docker/linumiq-invoice/git && git pull
|
||||
cd /docker/linumiq-invoice/dev && docker compose up -d --build # update dev
|
||||
cd /docker/linumiq-invoice/prod && docker compose up -d --build # update prod
|
||||
```
|
||||
|
||||
Run only the env you want to update.
|
||||
|
||||
## Pushing config / extension changes to Shopify
|
||||
|
||||
From your dev machine (after `git pull` to keep configs in sync):
|
||||
|
||||
```bash
|
||||
# DEV → linumiq-dev.myshopify.com
|
||||
npx shopify app config use shopify.app.dev.toml
|
||||
npx shopify app deploy --allow-updates
|
||||
|
||||
# PROD → shop.linumiq.com
|
||||
npx shopify app config use shopify.app.prod.toml
|
||||
npx shopify app deploy --allow-updates
|
||||
```
|
||||
|
||||
The currently selected config is stored in `.shopify/project.json` (gitignored), so each developer machine remembers its own choice.
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
image: linumiq-invoice:dev
|
||||
container_name: linumiq-invoice-dev
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.dev
|
||||
environment:
|
||||
# SQLite file lives on a bind mount so it survives image rebuilds.
|
||||
DATABASE_URL: "file:/data/prod.sqlite"
|
||||
NODE_ENV: production
|
||||
PORT: "3000"
|
||||
volumes:
|
||||
- /docker/linumiq-invoice/dev/data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/healthz", "||", "exit", "0"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- caddy_net
|
||||
|
||||
networks:
|
||||
caddy_net:
|
||||
name: caddy_net
|
||||
external: true
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
image: linumiq-invoice:prod
|
||||
container_name: linumiq-invoice-prod
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.prod
|
||||
environment:
|
||||
# SQLite file lives on a bind mount so it survives image rebuilds.
|
||||
DATABASE_URL: "file:/data/prod.sqlite"
|
||||
NODE_ENV: production
|
||||
PORT: "3000"
|
||||
volumes:
|
||||
- /docker/linumiq-invoice/prod/data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/healthz", "||", "exit", "0"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- caddy_net
|
||||
|
||||
networks:
|
||||
caddy_net:
|
||||
name: caddy_net
|
||||
external: true
|
||||
Reference in New Issue
Block a user