create-rykira-app 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +162 -156
- package/package.json +1 -1
- package/template/README.md +44 -21
- package/template/apps/admin/nixpacks.toml +1 -1
- package/template/apps/web/nixpacks.toml +1 -1
- package/template/docker-compose.yml +19 -0
- package/deployment.md +0 -168
- package/how-to-deploy-package.md +0 -132
- package/package-version-update.md +0 -121
- package/starter-usage.md +0 -132
- package/template/.github/workflows/ci.yml +0 -60
- package/template/infrastructure/docker/Dockerfile.admin +0 -36
- package/template/infrastructure/docker/Dockerfile.api +0 -36
- package/template/infrastructure/docker/Dockerfile.web +0 -48
- package/template/infrastructure/docker/compose.dev.yml +0 -50
- package/template/infrastructure/docker/compose.prod.yml +0 -119
- package/template/infrastructure/scripts/deploy.sh +0 -3
- package/template/infrastructure/scripts/dev.sh +0 -5
- package/template/infrastructure/scripts/init-traefik.sh +0 -10
- package/template/infrastructure/scripts/setup-server.sh +0 -25
- package/template/infrastructure/scripts/update.sh +0 -7
- package/template/infrastructure/traefik/acme.json +0 -0
- package/template/infrastructure/traefik/dynamic.yml +0 -23
- package/template/infrastructure/traefik/traefik.yml +0 -26
package/bin/cli.js
CHANGED
|
@@ -1,156 +1,162 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import inquirer from 'inquirer';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import Handlebars from 'handlebars';
|
|
11
|
-
import { createRequire } from 'module';
|
|
12
|
-
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
const pkg = require('../package.json');
|
|
15
|
-
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
|
-
|
|
19
|
-
const program = new Command();
|
|
20
|
-
|
|
21
|
-
program
|
|
22
|
-
.name('create-rykira-app')
|
|
23
|
-
.description('Scaffold a new Rykira Starter Package project')
|
|
24
|
-
.version(pkg.version)
|
|
25
|
-
.argument('[project-directory]', 'Directory to create the project in')
|
|
26
|
-
.action(async (projectDirectory) => {
|
|
27
|
-
console.log(chalk.bold.blue('🚀 Welcome to Rykira Starter App Generator!'));
|
|
28
|
-
|
|
29
|
-
let targetDir = projectDirectory;
|
|
30
|
-
|
|
31
|
-
if (!targetDir) {
|
|
32
|
-
const answers = await inquirer.prompt([
|
|
33
|
-
{
|
|
34
|
-
type: 'input',
|
|
35
|
-
name: 'projectName',
|
|
36
|
-
message: 'What is the name of your project?',
|
|
37
|
-
default: 'my-rykira-app',
|
|
38
|
-
validate: (input) => {
|
|
39
|
-
if (/^([a-z0-9\-\_\.]+)$/.test(input)) return true;
|
|
40
|
-
return 'Project name may only include lower-case letters, numbers, dashes, underscores, and dots.';
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
]);
|
|
44
|
-
targetDir = answers.projectName;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const rootDir = path.resolve(process.cwd(), targetDir);
|
|
48
|
-
|
|
49
|
-
if (fs.existsSync(rootDir)) {
|
|
50
|
-
console.error(chalk.red(`Error: Directory ${targetDir} already exists.`));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const promptAnswers = await inquirer.prompt([
|
|
55
|
-
{
|
|
56
|
-
type: 'input',
|
|
57
|
-
name: 'description',
|
|
58
|
-
message: 'Project description:',
|
|
59
|
-
default: 'A modern full-stack application',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: 'input',
|
|
63
|
-
name: 'author',
|
|
64
|
-
message: 'Author name:',
|
|
65
|
-
default: 'Rykira User',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
type: 'confirm',
|
|
69
|
-
name: 'includeAdmin',
|
|
70
|
-
message: 'Include Admin Panel?',
|
|
71
|
-
default: true,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: 'confirm',
|
|
75
|
-
name: 'includeApi',
|
|
76
|
-
message: 'Include Express API?',
|
|
77
|
-
default: true,
|
|
78
|
-
}
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
const spinner = ora(`Creating project in ${chalk.green(rootDir)}...`).start();
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
// Copy template files
|
|
85
|
-
const templateDir = path.resolve(__dirname, '../template');
|
|
86
|
-
await fs.copy(templateDir, rootDir, {
|
|
87
|
-
filter: (src) => {
|
|
88
|
-
// Avoid copying node_modules and build artifacts
|
|
89
|
-
if (src.includes('node_modules')) return false;
|
|
90
|
-
|
|
91
|
-
const basename = path.basename(src);
|
|
92
|
-
return !['.next', '.turbo', 'dist', 'coverage', 'build', '.DS_Store'].includes(basename);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Process Handlebars templates
|
|
97
|
-
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
98
|
-
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
|
|
99
|
-
const template = Handlebars.compile(packageJsonContent);
|
|
100
|
-
const result = template({
|
|
101
|
-
projectName: path.basename(rootDir),
|
|
102
|
-
projectDescription: promptAnswers.description,
|
|
103
|
-
authorName: promptAnswers.author,
|
|
104
|
-
});
|
|
105
|
-
await fs.writeFile(packageJsonPath, result);
|
|
106
|
-
|
|
107
|
-
// Handle Admin Panel removal
|
|
108
|
-
if (!promptAnswers.includeAdmin) {
|
|
109
|
-
spinner.text = 'Removing Admin Panel...';
|
|
110
|
-
await fs.remove(path.join(rootDir, 'apps/admin'));
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const composeDevPath = path.join(rootDir, '
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
if (fs.existsSync(filePath)) {
|
|
128
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
projectName: path.basename(rootDir)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
console.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import Handlebars from 'handlebars';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pkg = require('../package.json');
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const program = new Command();
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.name('create-rykira-app')
|
|
23
|
+
.description('Scaffold a new Rykira Starter Package project')
|
|
24
|
+
.version(pkg.version)
|
|
25
|
+
.argument('[project-directory]', 'Directory to create the project in')
|
|
26
|
+
.action(async (projectDirectory) => {
|
|
27
|
+
console.log(chalk.bold.blue('🚀 Welcome to Rykira Starter App Generator!'));
|
|
28
|
+
|
|
29
|
+
let targetDir = projectDirectory;
|
|
30
|
+
|
|
31
|
+
if (!targetDir) {
|
|
32
|
+
const answers = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'projectName',
|
|
36
|
+
message: 'What is the name of your project?',
|
|
37
|
+
default: 'my-rykira-app',
|
|
38
|
+
validate: (input) => {
|
|
39
|
+
if (/^([a-z0-9\-\_\.]+)$/.test(input)) return true;
|
|
40
|
+
return 'Project name may only include lower-case letters, numbers, dashes, underscores, and dots.';
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
targetDir = answers.projectName;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rootDir = path.resolve(process.cwd(), targetDir);
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(rootDir)) {
|
|
50
|
+
console.error(chalk.red(`Error: Directory ${targetDir} already exists.`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const promptAnswers = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'description',
|
|
58
|
+
message: 'Project description:',
|
|
59
|
+
default: 'A modern full-stack application',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'input',
|
|
63
|
+
name: 'author',
|
|
64
|
+
message: 'Author name:',
|
|
65
|
+
default: 'Rykira User',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'includeAdmin',
|
|
70
|
+
message: 'Include Admin Panel?',
|
|
71
|
+
default: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'confirm',
|
|
75
|
+
name: 'includeApi',
|
|
76
|
+
message: 'Include Express API?',
|
|
77
|
+
default: true,
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const spinner = ora(`Creating project in ${chalk.green(rootDir)}...`).start();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Copy template files
|
|
85
|
+
const templateDir = path.resolve(__dirname, '../template');
|
|
86
|
+
await fs.copy(templateDir, rootDir, {
|
|
87
|
+
filter: (src) => {
|
|
88
|
+
// Avoid copying node_modules and build artifacts
|
|
89
|
+
if (src.includes('node_modules')) return false;
|
|
90
|
+
|
|
91
|
+
const basename = path.basename(src);
|
|
92
|
+
return !['.next', '.turbo', 'dist', 'coverage', 'build', '.DS_Store'].includes(basename);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Process Handlebars templates for package.json
|
|
97
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
98
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
|
|
99
|
+
const template = Handlebars.compile(packageJsonContent);
|
|
100
|
+
const result = template({
|
|
101
|
+
projectName: path.basename(rootDir),
|
|
102
|
+
projectDescription: promptAnswers.description,
|
|
103
|
+
authorName: promptAnswers.author,
|
|
104
|
+
});
|
|
105
|
+
await fs.writeFile(packageJsonPath, result);
|
|
106
|
+
|
|
107
|
+
// Handle Admin Panel removal
|
|
108
|
+
if (!promptAnswers.includeAdmin) {
|
|
109
|
+
spinner.text = 'Removing Admin Panel...';
|
|
110
|
+
await fs.remove(path.join(rootDir, 'apps/admin'));
|
|
111
|
+
// Note: You might need to clean up pnpm-workspace.yaml or turbo.json if strict
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle API removal
|
|
115
|
+
if (!promptAnswers.includeApi) {
|
|
116
|
+
spinner.text = 'Removing Express API...';
|
|
117
|
+
await fs.remove(path.join(rootDir, 'apps/api'));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Deployment Variables (Forced to Coolify)
|
|
121
|
+
const isCoolify = true;
|
|
122
|
+
|
|
123
|
+
// Update Compose files with project name and deployment logic
|
|
124
|
+
const composeDevPath = path.join(rootDir, 'docker-compose.yml');
|
|
125
|
+
|
|
126
|
+
const updateFileWithContext = async (filePath) => {
|
|
127
|
+
if (fs.existsSync(filePath)) {
|
|
128
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
129
|
+
const compiledTemplate = Handlebars.compile(content);
|
|
130
|
+
const compiledResult = compiledTemplate({
|
|
131
|
+
projectName: path.basename(rootDir),
|
|
132
|
+
isCoolify,
|
|
133
|
+
includeAdmin: promptAnswers.includeAdmin,
|
|
134
|
+
includeApi: promptAnswers.includeApi,
|
|
135
|
+
deploymentStrategy: 'coolify'
|
|
136
|
+
});
|
|
137
|
+
await fs.writeFile(filePath, compiledResult);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await updateFileWithContext(composeDevPath);
|
|
142
|
+
|
|
143
|
+
// Update README.md
|
|
144
|
+
const readmePath = path.join(rootDir, 'README.md');
|
|
145
|
+
await updateFileWithContext(readmePath);
|
|
146
|
+
|
|
147
|
+
spinner.succeed(chalk.green('Project created successfully!'));
|
|
148
|
+
|
|
149
|
+
console.log('\nNext steps:');
|
|
150
|
+
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
151
|
+
console.log(chalk.cyan(' pnpm install'));
|
|
152
|
+
console.log(chalk.cyan(' cp .env.example .env'));
|
|
153
|
+
console.log(chalk.cyan(' pnpm dev'));
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
spinner.fail(chalk.red('Failed to create project'));
|
|
157
|
+
console.error(error);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
This is a Next.js monorepo template with shadcn/ui.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
This is a Next.js monorepo template with shadcn/ui.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. Start the development server:
|
|
13
|
+
```bash
|
|
14
|
+
pnpm dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Deployment
|
|
18
|
+
|
|
19
|
+
This project is configured to be deployed easily using [Coolify](https://coolify.io/).
|
|
20
|
+
|
|
21
|
+
### Coolify Setup
|
|
22
|
+
1. Create a project in your Coolify dashboard.
|
|
23
|
+
2. Connect this Git repository.
|
|
24
|
+
3. Add a resource for each application (`web`, `admin`, `api`).
|
|
25
|
+
4. Set the Base Directory to `/` (root) for all of them.
|
|
26
|
+
5. Set the respective build and start commands for each app (e.g. `pnpm install && pnpm turbo build --filter=web...` and `pnpm --filter web start`).
|
|
27
|
+
|
|
28
|
+
## Adding components
|
|
29
|
+
|
|
30
|
+
To add components to your app, run the following command at the root of your `web` app:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm dlx shadcn@latest add button -c apps/web
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This will place the ui components in the `packages/ui/src/components` directory.
|
|
37
|
+
|
|
38
|
+
## Using components
|
|
39
|
+
|
|
40
|
+
To use the components in your app, import them from the `ui` package.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { Button } from "@workspace/ui/components/button";
|
|
44
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: "3.9"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
|
|
5
|
+
postgres:
|
|
6
|
+
image: postgres:16
|
|
7
|
+
container_name: {{projectName}}-postgres-dev
|
|
8
|
+
environment:
|
|
9
|
+
POSTGRES_DB: ${POSTGRES_DB}
|
|
10
|
+
POSTGRES_USER: ${POSTGRES_USER}
|
|
11
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
12
|
+
ports:
|
|
13
|
+
- "5432:5432"
|
|
14
|
+
|
|
15
|
+
redis:
|
|
16
|
+
image: redis:7
|
|
17
|
+
container_name: {{projectName}}-redis-dev
|
|
18
|
+
ports:
|
|
19
|
+
- "6379:6379"
|
package/deployment.md
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Deployment Guide
|
|
2
|
-
|
|
3
|
-
This guide covers how to deploy the Rykira Starter Package application using Coolify (automated) or manually using Docker and Traefik on a VPS.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Prerequisites](#prerequisites)
|
|
8
|
-
- [Environment Configuration](#environment-configuration)
|
|
9
|
-
- [Option 1: Deploy with Coolify (Recommended)](#option-1-deploy-with-coolify-recommended)
|
|
10
|
-
- [Option 2: Manual Deployment (Docker & Traefik)](#option-2-manual-deployment-docker--traefik)
|
|
11
|
-
- [Post-Deployment Verification](#post-deployment-verification)
|
|
12
|
-
- [Troubleshooting](#troubleshooting)
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Prerequisites
|
|
17
|
-
|
|
18
|
-
- **Domain Name**: A domain name pointing to your server IP (e.g., `example.com`, `*.example.com`).
|
|
19
|
-
- **VPS/Server**: A server running Ubuntu 22.04/24.04 (minimum 2GB RAM, 2 vCPUs recommended).
|
|
20
|
-
- **Git Repository**: Your project pushed to a Git provider (GitHub, GitLab, etc.).
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Environment Configuration
|
|
25
|
-
|
|
26
|
-
All deployments require a `.env` file. Use the `.env.example` as a template.
|
|
27
|
-
|
|
28
|
-
### Required Variables
|
|
29
|
-
|
|
30
|
-
```env
|
|
31
|
-
# Domain Configuration
|
|
32
|
-
DOMAIN=example.com
|
|
33
|
-
ACME_EMAIL=admin@example.com
|
|
34
|
-
|
|
35
|
-
# Postgres Configuration
|
|
36
|
-
POSTGRES_USER=postgres
|
|
37
|
-
POSTGRES_PASSWORD=secure_password_here
|
|
38
|
-
POSTGRES_DB=rykira
|
|
39
|
-
|
|
40
|
-
# Redis Configuration
|
|
41
|
-
REDIS_PASSWORD=secure_redis_password_here
|
|
42
|
-
|
|
43
|
-
# Database Connection Strings (Internal Docker Network)
|
|
44
|
-
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
|
45
|
-
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
|
|
46
|
-
|
|
47
|
-
# App Secrets
|
|
48
|
-
NEXTAUTH_SECRET=generate_a_long_random_string
|
|
49
|
-
auth_secret=generate_a_long_random_string
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Option 1: Deploy with Coolify (Recommended)
|
|
55
|
-
|
|
56
|
-
Coolify is an open-source, self-hostable Heroku alternative that makes deployment easy.
|
|
57
|
-
|
|
58
|
-
### Step 1: Prepare Your Project
|
|
59
|
-
1. Ensure `nixpacks.toml` files exist in `apps/web`, `apps/admin`, and `apps/api` (included in this template).
|
|
60
|
-
2. Push your code to a GitHub repository.
|
|
61
|
-
|
|
62
|
-
### Step 2: Configure Coolify
|
|
63
|
-
1. **Create a Project** in your Coolify dashboard.
|
|
64
|
-
2. **Add a Resource**: Select **Git Repository** (Private or Public).
|
|
65
|
-
3. **Select Repository**: Choose your project repo.
|
|
66
|
-
|
|
67
|
-
### Step 3: Add Services
|
|
68
|
-
You will deploy each app as a separate resource, or use a Docker Compose stack.
|
|
69
|
-
|
|
70
|
-
#### Method A: Docker Compose Stack (Easiest)
|
|
71
|
-
1. In Coolify, select **Docker Compose**.
|
|
72
|
-
2. Paste the contents of `infrastructure/docker/compose.prod.yml`.
|
|
73
|
-
3. **IMPORTANT**: Replace `{{projectName}}` with your actual project name in the Compose file content before pasting, or ensure you set it as a variable if Coolify supports it.
|
|
74
|
-
4. Go to **Environment Variables** in Coolify and add all variables from your `.env` file.
|
|
75
|
-
5. Click **Deploy**.
|
|
76
|
-
|
|
77
|
-
#### Method B: Individual Services (Nixpacks)
|
|
78
|
-
1. **Web App (`apps/web`)**:
|
|
79
|
-
- **Build Pack**: Nixpacks
|
|
80
|
-
- **Base Directory**: `/`
|
|
81
|
-
- **Build Command**: `pnpm install && pnpm turbo build --filter=web...`
|
|
82
|
-
- **Start Command**: `pnpm start:web`
|
|
83
|
-
- **Environment Variables**: Add all required envs.
|
|
84
|
-
- **Domains**: Set to `https://app.example.com`.
|
|
85
|
-
|
|
86
|
-
2. **API (`apps/api`)**:
|
|
87
|
-
- **Build Pack**: Nixpacks
|
|
88
|
-
- **Base Directory**: `/`
|
|
89
|
-
- **Build Command**: `pnpm install && pnpm turbo build --filter=api...`
|
|
90
|
-
- **Start Command**: `node apps/api/dist/index.js`
|
|
91
|
-
- **Environment Variables**: Add all required envs.
|
|
92
|
-
- **Domains**: Set to `https://api.example.com`.
|
|
93
|
-
|
|
94
|
-
3. **Database & Redis**:
|
|
95
|
-
- Use Coolify's **Databases** tab to provision PostgreSQL and Redis.
|
|
96
|
-
- Update your App's `DATABASE_URL` and `REDIS_URL` to point to these internal Coolify databases.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Option 2: Manual Deployment (Docker & Traefik)
|
|
101
|
-
|
|
102
|
-
This method gives you full control using standard Docker Compose and Traefik v3 as a reverse proxy.
|
|
103
|
-
|
|
104
|
-
### Step 1: Server Setup
|
|
105
|
-
1. SSH into your server.
|
|
106
|
-
2. Clone your repository:
|
|
107
|
-
```bash
|
|
108
|
-
git clone https://github.com/your/repo.git my-app
|
|
109
|
-
cd my-app
|
|
110
|
-
```
|
|
111
|
-
3. Run the setup script to install Docker and configure UFW firewall:
|
|
112
|
-
```bash
|
|
113
|
-
chmod +x infrastructure/scripts/setup-server.sh
|
|
114
|
-
./infrastructure/scripts/setup-server.sh
|
|
115
|
-
```
|
|
116
|
-
*You may need to log out and log back in for Docker group changes to take effect.*
|
|
117
|
-
|
|
118
|
-
### Step 2: Traefik Initialization
|
|
119
|
-
1. Initialize the ACME JSON file for SSL certificates:
|
|
120
|
-
```bash
|
|
121
|
-
chmod +x infrastructure/scripts/init-traefik.sh
|
|
122
|
-
./infrastructure/scripts/init-traefik.sh
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Step 3: Deploy
|
|
126
|
-
1. Create your `.env` file:
|
|
127
|
-
```bash
|
|
128
|
-
cp .env.example .env
|
|
129
|
-
nano .env
|
|
130
|
-
# Fill in your secrets and domain
|
|
131
|
-
```
|
|
132
|
-
2. Run the deployment script:
|
|
133
|
-
```bash
|
|
134
|
-
chmod +x infrastructure/scripts/deploy.sh
|
|
135
|
-
./infrastructure/scripts/deploy.sh
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Step 4: Verify
|
|
139
|
-
- Check running containers: `docker compose ps`
|
|
140
|
-
- View logs: `docker compose logs -f`
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Post-Deployment Verification
|
|
145
|
-
|
|
146
|
-
1. **Check Health**:
|
|
147
|
-
- Visit `https://api.example.com/health` (should return JSON status).
|
|
148
|
-
2. **Check SSL**:
|
|
149
|
-
- Visit `https://app.example.com` - ensure lock icon is present.
|
|
150
|
-
3. **Database Connection**:
|
|
151
|
-
- Verify the API can read/write to the database (try logging in or creating a user).
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## Troubleshooting
|
|
156
|
-
|
|
157
|
-
### "Acme.json permission denied"
|
|
158
|
-
- Run: `chmod 600 infrastructure/traefik/acme.json`
|
|
159
|
-
|
|
160
|
-
### "Gateway Timeout" (504)
|
|
161
|
-
- Check if the app container is running: `docker compose ps`
|
|
162
|
-
- Check logs: `docker compose logs web`
|
|
163
|
-
- Ensure the port in `compose.prod.yml` matches the app's listening port (default 3000 for web, 4000 for api).
|
|
164
|
-
|
|
165
|
-
### Database Connection Error
|
|
166
|
-
- Ensure `DATABASE_URL` is using the container name `postgres` (not `localhost`) if running inside Docker.
|
|
167
|
-
- Check if Postgres is healthy: `docker compose ps postgres`
|
|
168
|
-
|