nvoi 0.1.5

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.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +19 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +151 -0
  5. data/Makefile +26 -0
  6. data/Rakefile +16 -0
  7. data/doc/config-schema.yaml +357 -0
  8. data/examples/apex-wildcard/deploy.yml +68 -0
  9. data/examples/golang/.gitignore +19 -0
  10. data/examples/golang/Dockerfile +43 -0
  11. data/examples/golang/README.md +59 -0
  12. data/examples/golang/deploy.enc +0 -0
  13. data/examples/golang/deploy.yml +54 -0
  14. data/examples/golang/go.mod +39 -0
  15. data/examples/golang/go.sum +96 -0
  16. data/examples/golang/main.go +177 -0
  17. data/examples/golang/models/user.go +17 -0
  18. data/examples/golang-postgres-multi/.gitignore +18 -0
  19. data/examples/golang-postgres-multi/Dockerfile +39 -0
  20. data/examples/golang-postgres-multi/README.md +211 -0
  21. data/examples/golang-postgres-multi/deploy.yml +67 -0
  22. data/examples/golang-postgres-multi/go.mod +45 -0
  23. data/examples/golang-postgres-multi/go.sum +108 -0
  24. data/examples/golang-postgres-multi/main.go +197 -0
  25. data/examples/golang-postgres-multi/models/user.go +17 -0
  26. data/examples/postgres-multi/.env.production.example +11 -0
  27. data/examples/postgres-multi/README.md +112 -0
  28. data/examples/postgres-multi/deploy.yml +74 -0
  29. data/examples/postgres-single/.env.production.example +11 -0
  30. data/examples/postgres-single/.gitignore +15 -0
  31. data/examples/postgres-single/Dockerfile +35 -0
  32. data/examples/postgres-single/README.md +76 -0
  33. data/examples/postgres-single/deploy.yml +56 -0
  34. data/examples/postgres-single/go.mod +45 -0
  35. data/examples/postgres-single/go.sum +108 -0
  36. data/examples/postgres-single/main.go +184 -0
  37. data/examples/rails-single/.dockerignore +51 -0
  38. data/examples/rails-single/.env.production.example +11 -0
  39. data/examples/rails-single/.github/dependabot.yml +12 -0
  40. data/examples/rails-single/.github/workflows/ci.yml +39 -0
  41. data/examples/rails-single/.gitignore +20 -0
  42. data/examples/rails-single/.node-version +1 -0
  43. data/examples/rails-single/.rubocop.yml +8 -0
  44. data/examples/rails-single/.ruby-version +1 -0
  45. data/examples/rails-single/Dockerfile +86 -0
  46. data/examples/rails-single/Gemfile +56 -0
  47. data/examples/rails-single/Gemfile.lock +350 -0
  48. data/examples/rails-single/Procfile.dev +3 -0
  49. data/examples/rails-single/README.md +17 -0
  50. data/examples/rails-single/Rakefile +6 -0
  51. data/examples/rails-single/app/assets/builds/.keep +0 -0
  52. data/examples/rails-single/app/assets/images/.keep +0 -0
  53. data/examples/rails-single/app/assets/stylesheets/application.tailwind.css +1 -0
  54. data/examples/rails-single/app/controllers/application_controller.rb +4 -0
  55. data/examples/rails-single/app/controllers/concerns/.keep +0 -0
  56. data/examples/rails-single/app/controllers/users_controller.rb +19 -0
  57. data/examples/rails-single/app/helpers/application_helper.rb +2 -0
  58. data/examples/rails-single/app/javascript/application.js +3 -0
  59. data/examples/rails-single/app/javascript/controllers/application.js +9 -0
  60. data/examples/rails-single/app/javascript/controllers/hello_controller.js +7 -0
  61. data/examples/rails-single/app/javascript/controllers/index.js +8 -0
  62. data/examples/rails-single/app/jobs/application_job.rb +7 -0
  63. data/examples/rails-single/app/mailers/application_mailer.rb +4 -0
  64. data/examples/rails-single/app/models/application_record.rb +3 -0
  65. data/examples/rails-single/app/models/concerns/.keep +0 -0
  66. data/examples/rails-single/app/models/user.rb +2 -0
  67. data/examples/rails-single/app/views/layouts/application.html.erb +28 -0
  68. data/examples/rails-single/app/views/layouts/mailer.html.erb +13 -0
  69. data/examples/rails-single/app/views/layouts/mailer.text.erb +1 -0
  70. data/examples/rails-single/app/views/pwa/manifest.json.erb +22 -0
  71. data/examples/rails-single/app/views/pwa/service-worker.js +26 -0
  72. data/examples/rails-single/app/views/users/index.html.erb +38 -0
  73. data/examples/rails-single/bin/brakeman +7 -0
  74. data/examples/rails-single/bin/bundle +109 -0
  75. data/examples/rails-single/bin/dev +11 -0
  76. data/examples/rails-single/bin/docker-entrypoint +14 -0
  77. data/examples/rails-single/bin/jobs +6 -0
  78. data/examples/rails-single/bin/kamal +27 -0
  79. data/examples/rails-single/bin/rails +4 -0
  80. data/examples/rails-single/bin/rake +4 -0
  81. data/examples/rails-single/bin/rubocop +8 -0
  82. data/examples/rails-single/bin/setup +37 -0
  83. data/examples/rails-single/bin/thrust +5 -0
  84. data/examples/rails-single/bun.lock +224 -0
  85. data/examples/rails-single/config/application.rb +42 -0
  86. data/examples/rails-single/config/boot.rb +4 -0
  87. data/examples/rails-single/config/cable.yml +17 -0
  88. data/examples/rails-single/config/cache.yml +16 -0
  89. data/examples/rails-single/config/credentials.yml.enc +1 -0
  90. data/examples/rails-single/config/database.yml +100 -0
  91. data/examples/rails-single/config/environment.rb +5 -0
  92. data/examples/rails-single/config/environments/development.rb +69 -0
  93. data/examples/rails-single/config/environments/production.rb +87 -0
  94. data/examples/rails-single/config/environments/test.rb +50 -0
  95. data/examples/rails-single/config/initializers/assets.rb +7 -0
  96. data/examples/rails-single/config/initializers/content_security_policy.rb +25 -0
  97. data/examples/rails-single/config/initializers/filter_parameter_logging.rb +8 -0
  98. data/examples/rails-single/config/initializers/inflections.rb +16 -0
  99. data/examples/rails-single/config/locales/en.yml +31 -0
  100. data/examples/rails-single/config/puma.rb +41 -0
  101. data/examples/rails-single/config/queue.yml +18 -0
  102. data/examples/rails-single/config/recurring.yml +15 -0
  103. data/examples/rails-single/config/routes.rb +4 -0
  104. data/examples/rails-single/config.ru +6 -0
  105. data/examples/rails-single/db/cable_schema.rb +11 -0
  106. data/examples/rails-single/db/cache_schema.rb +12 -0
  107. data/examples/rails-single/db/migrate/20251123095526_create_users.rb +10 -0
  108. data/examples/rails-single/db/queue_schema.rb +129 -0
  109. data/examples/rails-single/db/seeds.rb +9 -0
  110. data/examples/rails-single/deploy.yml +57 -0
  111. data/examples/rails-single/lib/tasks/.keep +0 -0
  112. data/examples/rails-single/log/.keep +0 -0
  113. data/examples/rails-single/package.json +17 -0
  114. data/examples/rails-single/public/400.html +114 -0
  115. data/examples/rails-single/public/404.html +114 -0
  116. data/examples/rails-single/public/406-unsupported-browser.html +114 -0
  117. data/examples/rails-single/public/422.html +114 -0
  118. data/examples/rails-single/public/500.html +114 -0
  119. data/examples/rails-single/public/icon.png +0 -0
  120. data/examples/rails-single/public/icon.svg +3 -0
  121. data/examples/rails-single/public/robots.txt +1 -0
  122. data/examples/rails-single/script/.keep +0 -0
  123. data/examples/rails-single/vendor/.keep +0 -0
  124. data/examples/rails-single/yarn.lock +188 -0
  125. data/exe/nvoi +6 -0
  126. data/lib/nvoi/cli.rb +190 -0
  127. data/lib/nvoi/cloudflare/client.rb +287 -0
  128. data/lib/nvoi/config/config.rb +248 -0
  129. data/lib/nvoi/config/env_resolver.rb +63 -0
  130. data/lib/nvoi/config/loader.rb +102 -0
  131. data/lib/nvoi/config/naming.rb +196 -0
  132. data/lib/nvoi/config/ssh_keys.rb +82 -0
  133. data/lib/nvoi/config/types.rb +274 -0
  134. data/lib/nvoi/constants.rb +59 -0
  135. data/lib/nvoi/credentials/crypto.rb +88 -0
  136. data/lib/nvoi/credentials/editor.rb +272 -0
  137. data/lib/nvoi/credentials/manager.rb +173 -0
  138. data/lib/nvoi/deployer/cleaner.rb +36 -0
  139. data/lib/nvoi/deployer/image_builder.rb +23 -0
  140. data/lib/nvoi/deployer/infrastructure.rb +126 -0
  141. data/lib/nvoi/deployer/orchestrator.rb +146 -0
  142. data/lib/nvoi/deployer/retry.rb +67 -0
  143. data/lib/nvoi/deployer/service_deployer.rb +311 -0
  144. data/lib/nvoi/deployer/tunnel_manager.rb +57 -0
  145. data/lib/nvoi/deployer/types.rb +8 -0
  146. data/lib/nvoi/errors.rb +67 -0
  147. data/lib/nvoi/k8s/renderer.rb +44 -0
  148. data/lib/nvoi/k8s/templates.rb +29 -0
  149. data/lib/nvoi/logger.rb +72 -0
  150. data/lib/nvoi/providers/aws.rb +403 -0
  151. data/lib/nvoi/providers/base.rb +111 -0
  152. data/lib/nvoi/providers/hetzner.rb +288 -0
  153. data/lib/nvoi/providers/hetzner_client.rb +170 -0
  154. data/lib/nvoi/remote/docker_manager.rb +203 -0
  155. data/lib/nvoi/remote/ssh_executor.rb +72 -0
  156. data/lib/nvoi/remote/volume_manager.rb +103 -0
  157. data/lib/nvoi/service/delete.rb +234 -0
  158. data/lib/nvoi/service/deploy.rb +80 -0
  159. data/lib/nvoi/service/exec.rb +144 -0
  160. data/lib/nvoi/service/provider.rb +36 -0
  161. data/lib/nvoi/steps/application_deployer.rb +26 -0
  162. data/lib/nvoi/steps/database_provisioner.rb +60 -0
  163. data/lib/nvoi/steps/k3s_cluster_setup.rb +105 -0
  164. data/lib/nvoi/steps/k3s_provisioner.rb +351 -0
  165. data/lib/nvoi/steps/server_provisioner.rb +43 -0
  166. data/lib/nvoi/steps/services_provisioner.rb +29 -0
  167. data/lib/nvoi/steps/tunnel_configurator.rb +66 -0
  168. data/lib/nvoi/steps/volume_provisioner.rb +154 -0
  169. data/lib/nvoi/version.rb +5 -0
  170. data/lib/nvoi.rb +79 -0
  171. data/templates/app-deployment.yaml.erb +102 -0
  172. data/templates/app-ingress.yaml.erb +20 -0
  173. data/templates/app-secret.yaml.erb +10 -0
  174. data/templates/app-service.yaml.erb +12 -0
  175. data/templates/db-statefulset.yaml.erb +76 -0
  176. data/templates/service-deployment.yaml.erb +91 -0
  177. data/templates/worker-deployment.yaml.erb +50 -0
  178. metadata +361 -0
@@ -0,0 +1,184 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "log"
7
+ "math/rand"
8
+ "net/http"
9
+ "os"
10
+ "os/signal"
11
+ "syscall"
12
+ "time"
13
+
14
+ "github.com/gin-gonic/gin"
15
+ "gorm.io/driver/postgres"
16
+ "gorm.io/gorm"
17
+ )
18
+
19
+ type User struct {
20
+ ID uint `json:"id" gorm:"primaryKey"`
21
+ Name string `json:"name"`
22
+ Email string `json:"email" gorm:"uniqueIndex"`
23
+ CreatedAt time.Time `json:"created_at"`
24
+ UpdatedAt time.Time `json:"updated_at"`
25
+ }
26
+
27
+ var db *gorm.DB
28
+
29
+ func main() {
30
+ // Initialize database
31
+ var err error
32
+ db, err = initDB()
33
+ if err != nil {
34
+ log.Fatalf("Failed to initialize database: %v", err)
35
+ }
36
+
37
+ // Auto-migrate database schema
38
+ if err := db.AutoMigrate(&User{}); err != nil {
39
+ log.Fatalf("Failed to migrate database: %v", err)
40
+ }
41
+
42
+ // Setup router
43
+ router := setupRouter()
44
+
45
+ // Configure server
46
+ srv := &http.Server{
47
+ Addr: ":3000",
48
+ Handler: router,
49
+ }
50
+
51
+ // Start server in goroutine
52
+ go func() {
53
+ log.Println("Starting server on :3000")
54
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
55
+ log.Fatalf("Server failed to start: %v", err)
56
+ }
57
+ }()
58
+
59
+ // Wait for interrupt signal for graceful shutdown
60
+ quit := make(chan os.Signal, 1)
61
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
62
+ <-quit
63
+
64
+ log.Println("Shutting down server...")
65
+
66
+ // Graceful shutdown with 5 second timeout
67
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
68
+ defer cancel()
69
+
70
+ if err := srv.Shutdown(ctx); err != nil {
71
+ log.Fatalf("Server forced to shutdown: %v", err)
72
+ }
73
+
74
+ log.Println("Server exited")
75
+ }
76
+
77
+ func initDB() (*gorm.DB, error) {
78
+ dsn := os.Getenv("DATABASE_URL")
79
+ if dsn == "" {
80
+ return nil, fmt.Errorf("DATABASE_URL environment variable is required")
81
+ }
82
+
83
+ db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
84
+ if err != nil {
85
+ return nil, fmt.Errorf("failed to connect to database: %w", err)
86
+ }
87
+
88
+ log.Println("Database connected successfully")
89
+ return db, nil
90
+ }
91
+
92
+ func setupRouter() *gin.Engine {
93
+ // Set Gin mode from environment
94
+ if mode := os.Getenv("GIN_MODE"); mode != "" {
95
+ gin.SetMode(mode)
96
+ }
97
+
98
+ router := gin.Default()
99
+
100
+ // Health check endpoint (required for deployment)
101
+ router.GET("/health", healthCheck)
102
+
103
+ // Main endpoint: creates user on every visit, returns all users
104
+ router.GET("/", handleVisit)
105
+
106
+ return router
107
+ }
108
+
109
+ // Health check handler (required for zero-downtime deployments)
110
+ func healthCheck(c *gin.Context) {
111
+ // Check database connectivity
112
+ sqlDB, err := db.DB()
113
+ if err != nil {
114
+ c.JSON(http.StatusServiceUnavailable, gin.H{
115
+ "status": "unhealthy",
116
+ "error": "database connection failed",
117
+ })
118
+ return
119
+ }
120
+
121
+ if err := sqlDB.Ping(); err != nil {
122
+ c.JSON(http.StatusServiceUnavailable, gin.H{
123
+ "status": "unhealthy",
124
+ "error": "database ping failed",
125
+ })
126
+ return
127
+ }
128
+
129
+ c.JSON(http.StatusOK, gin.H{
130
+ "status": "healthy",
131
+ "service": "go-postgres-demo",
132
+ "database": "postgresql",
133
+ "time": time.Now().Format(time.RFC3339),
134
+ })
135
+ }
136
+
137
+ // Main handler: creates a new user on every visit and returns all users
138
+ func handleVisit(c *gin.Context) {
139
+ // Create a new user with random name
140
+ newUser := User{
141
+ Name: generateRandomName(),
142
+ Email: fmt.Sprintf("user-%d@example.com", time.Now().UnixNano()),
143
+ }
144
+
145
+ if err := db.Create(&newUser).Error; err != nil {
146
+ c.JSON(http.StatusInternalServerError, gin.H{
147
+ "error": "Failed to create user",
148
+ })
149
+ return
150
+ }
151
+
152
+ // Fetch all users
153
+ var users []User
154
+ if err := db.Find(&users).Error; err != nil {
155
+ c.JSON(http.StatusInternalServerError, gin.H{
156
+ "error": "Failed to fetch users",
157
+ })
158
+ return
159
+ }
160
+
161
+ // Get deployment info
162
+ hostname, _ := os.Hostname()
163
+
164
+ // Return response
165
+ c.JSON(http.StatusOK, gin.H{
166
+ "hostname": hostname,
167
+ "message": "User created on this visit!",
168
+ "new_user": newUser,
169
+ "total_users": len(users),
170
+ "all_users": users,
171
+ })
172
+ }
173
+
174
+ // Generate random name for demo purposes
175
+ func generateRandomName() string {
176
+ firstNames := []string{"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack"}
177
+ lastNames := []string{"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"}
178
+
179
+ rand.Seed(time.Now().UnixNano())
180
+ firstName := firstNames[rand.Intn(len(firstNames))]
181
+ lastName := lastNames[rand.Intn(len(lastNames))]
182
+
183
+ return fmt.Sprintf("%s %s", firstName, lastName)
184
+ }
@@ -0,0 +1,51 @@
1
+ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2
+
3
+ # Ignore git directory.
4
+ /.git/
5
+ /.gitignore
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all default key files.
14
+ /config/master.key
15
+ /config/credentials/*.key
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /log/*
19
+ /tmp/*
20
+ !/log/.keep
21
+ !/tmp/.keep
22
+
23
+ # Ignore pidfiles, but keep the directory.
24
+ /tmp/pids/*
25
+ !/tmp/pids/.keep
26
+
27
+ # Ignore storage (uploaded files in development and any SQLite databases).
28
+ /storage/*
29
+ !/storage/.keep
30
+ /tmp/storage/*
31
+ !/tmp/storage/.keep
32
+
33
+ # Ignore assets.
34
+ /node_modules/
35
+ /app/assets/builds/*
36
+ !/app/assets/builds/.keep
37
+ /public/assets
38
+
39
+ # Ignore CI service files.
40
+ /.github
41
+
42
+ # Ignore Kamal files.
43
+ /config/deploy*.yml
44
+ /.kamal
45
+
46
+ # Ignore development files
47
+ /.devcontainer
48
+
49
+ # Ignore Docker-related files
50
+ /.dockerignore
51
+ /Dockerfile*
@@ -0,0 +1,11 @@
1
+ # Provider credentials
2
+ CLOUDFLARE_API_TOKEN=your_cloudflare_token_here
3
+ CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id_here
4
+ HETZNER_API_TOKEN=your_hetzner_token_here
5
+
6
+ # Database password
7
+ DB_PASSWORD=your_secure_db_password_here
8
+
9
+ # Rails secrets
10
+ SECRET_KEY_BASE=generate_with_rails_secret_or_openssl_rand_hex_64
11
+ RAILS_MASTER_KEY=copy_from_config_master_key
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: daily
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ scan_ruby:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: .ruby-version
20
+ bundler-cache: true
21
+
22
+ - name: Scan for common Rails security vulnerabilities using static analysis
23
+ run: bin/brakeman --no-pager
24
+
25
+ lint:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - name: Checkout code
29
+ uses: actions/checkout@v4
30
+
31
+ - name: Set up Ruby
32
+ uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: .ruby-version
35
+ bundler-cache: true
36
+
37
+ - name: Lint code for consistent style
38
+ run: bin/rubocop -f github
39
+
@@ -0,0 +1,20 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Logs
5
+ log/*.log
6
+
7
+ # Temp files
8
+ tmp/
9
+
10
+ # Environment
11
+ .env*
12
+ !.env.production.example
13
+
14
+ # Secrets
15
+ config/master.key
16
+ config/credentials/*.key
17
+
18
+ # Build artifacts
19
+ app/assets/builds/*
20
+ !app/assets/builds/.keep
@@ -0,0 +1 @@
1
+ 22.16.0
@@ -0,0 +1,8 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ # Overwrite or add rules to create your own house style
5
+ #
6
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
+ # Layout/SpaceInsideArrayLiteralBrackets:
8
+ # Enabled: false
@@ -0,0 +1 @@
1
+ ruby-3.2.3
@@ -0,0 +1,86 @@
1
+ # syntax=docker/dockerfile:1
2
+ # check=error=true
3
+
4
+ # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
5
+ # docker build -t rails_single .
6
+ # docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name rails_single rails_single
7
+
8
+ # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
9
+
10
+ # Make sure RUBY_VERSION matches the Ruby version in .ruby-version
11
+ ARG RUBY_VERSION=3.2.3
12
+ FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
13
+
14
+ # Rails app lives here
15
+ WORKDIR /rails
16
+
17
+ # Install base packages
18
+ RUN apt-get update -qq && \
19
+ apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client && \
20
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
21
+
22
+ # Set production environment
23
+ ENV RAILS_ENV="production" \
24
+ BUNDLE_DEPLOYMENT="1" \
25
+ BUNDLE_PATH="/usr/local/bundle" \
26
+ BUNDLE_WITHOUT="development"
27
+
28
+ # Throw-away build stage to reduce size of final image
29
+ FROM base AS build
30
+
31
+ # Install packages needed to build gems and node modules
32
+ RUN apt-get update -qq && \
33
+ apt-get install --no-install-recommends -y build-essential git libpq-dev libyaml-dev node-gyp pkg-config python-is-python3 && \
34
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
35
+
36
+ # Install JavaScript dependencies
37
+ ARG NODE_VERSION=22.16.0
38
+ ARG YARN_VERSION=1.22.22
39
+ ENV PATH=/usr/local/node/bin:$PATH
40
+ RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
41
+ /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
42
+ npm install -g yarn@$YARN_VERSION && \
43
+ rm -rf /tmp/node-build-master
44
+
45
+ # Install application gems
46
+ COPY Gemfile Gemfile.lock ./
47
+ RUN bundle install && \
48
+ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
49
+ bundle exec bootsnap precompile --gemfile
50
+
51
+ # Install node modules
52
+ COPY package.json yarn.lock ./
53
+ RUN yarn install --immutable
54
+
55
+ # Copy application code
56
+ COPY . .
57
+
58
+ # Precompile bootsnap code for faster boot times
59
+ RUN bundle exec bootsnap precompile app/ lib/
60
+
61
+ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
62
+ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
63
+
64
+
65
+ RUN rm -rf node_modules
66
+
67
+
68
+ # Final stage for app image
69
+ FROM base
70
+
71
+ # Copy built artifacts: gems, application
72
+ COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
73
+ COPY --from=build /rails /rails
74
+
75
+ # Run and own only the runtime files as a non-root user for security
76
+ RUN groupadd --system --gid 1000 rails && \
77
+ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
78
+ chown -R rails:rails db log tmp
79
+ USER 1000:1000
80
+
81
+ # Entrypoint prepares the database.
82
+ ENTRYPOINT ["/rails/bin/docker-entrypoint"]
83
+
84
+ # Start server via Thruster by default, this can be overwritten at runtime
85
+ EXPOSE 80
86
+ CMD ["./bin/thrust", "./bin/rails", "server"]
@@ -0,0 +1,56 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
4
+ gem "rails", "~> 8.0.4"
5
+ # The modern asset pipeline for Rails [https://github.com/rails/propshaft]
6
+ gem "propshaft"
7
+ # Use postgresql as the database for Active Record
8
+ gem "pg", "~> 1.1"
9
+ # Use the Puma web server [https://github.com/puma/puma]
10
+ gem "puma", ">= 5.0"
11
+ # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
12
+ gem "jsbundling-rails"
13
+ # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
14
+ gem "turbo-rails"
15
+ # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
16
+ gem "stimulus-rails"
17
+ # Bundle and process CSS [https://github.com/rails/cssbundling-rails]
18
+ gem "cssbundling-rails"
19
+ # Build JSON APIs with ease [https://github.com/rails/jbuilder]
20
+ gem "jbuilder"
21
+
22
+ # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
23
+ # gem "bcrypt", "~> 3.1.7"
24
+
25
+ # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
26
+ gem "tzinfo-data", platforms: %i[ windows jruby ]
27
+
28
+ # Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
29
+ gem "solid_cache"
30
+ gem "solid_queue"
31
+ gem "solid_cable"
32
+
33
+ # Reduces boot times through caching; required in config/boot.rb
34
+ gem "bootsnap", require: false
35
+
36
+ # Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
37
+ gem "kamal", require: false
38
+
39
+ # Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
40
+ gem "thruster", require: false
41
+
42
+ group :development, :test do
43
+ # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
44
+ gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
45
+
46
+ # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
47
+ gem "brakeman", require: false
48
+
49
+ # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
50
+ gem "rubocop-rails-omakase", require: false
51
+ end
52
+
53
+ group :development do
54
+ # Use console on exceptions pages [https://github.com/rails/web-console]
55
+ gem "web-console"
56
+ end