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.
- checksums.yaml +7 -0
- data/.rubocop.yml +19 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +151 -0
- data/Makefile +26 -0
- data/Rakefile +16 -0
- data/doc/config-schema.yaml +357 -0
- data/examples/apex-wildcard/deploy.yml +68 -0
- data/examples/golang/.gitignore +19 -0
- data/examples/golang/Dockerfile +43 -0
- data/examples/golang/README.md +59 -0
- data/examples/golang/deploy.enc +0 -0
- data/examples/golang/deploy.yml +54 -0
- data/examples/golang/go.mod +39 -0
- data/examples/golang/go.sum +96 -0
- data/examples/golang/main.go +177 -0
- data/examples/golang/models/user.go +17 -0
- data/examples/golang-postgres-multi/.gitignore +18 -0
- data/examples/golang-postgres-multi/Dockerfile +39 -0
- data/examples/golang-postgres-multi/README.md +211 -0
- data/examples/golang-postgres-multi/deploy.yml +67 -0
- data/examples/golang-postgres-multi/go.mod +45 -0
- data/examples/golang-postgres-multi/go.sum +108 -0
- data/examples/golang-postgres-multi/main.go +197 -0
- data/examples/golang-postgres-multi/models/user.go +17 -0
- data/examples/postgres-multi/.env.production.example +11 -0
- data/examples/postgres-multi/README.md +112 -0
- data/examples/postgres-multi/deploy.yml +74 -0
- data/examples/postgres-single/.env.production.example +11 -0
- data/examples/postgres-single/.gitignore +15 -0
- data/examples/postgres-single/Dockerfile +35 -0
- data/examples/postgres-single/README.md +76 -0
- data/examples/postgres-single/deploy.yml +56 -0
- data/examples/postgres-single/go.mod +45 -0
- data/examples/postgres-single/go.sum +108 -0
- data/examples/postgres-single/main.go +184 -0
- data/examples/rails-single/.dockerignore +51 -0
- data/examples/rails-single/.env.production.example +11 -0
- data/examples/rails-single/.github/dependabot.yml +12 -0
- data/examples/rails-single/.github/workflows/ci.yml +39 -0
- data/examples/rails-single/.gitignore +20 -0
- data/examples/rails-single/.node-version +1 -0
- data/examples/rails-single/.rubocop.yml +8 -0
- data/examples/rails-single/.ruby-version +1 -0
- data/examples/rails-single/Dockerfile +86 -0
- data/examples/rails-single/Gemfile +56 -0
- data/examples/rails-single/Gemfile.lock +350 -0
- data/examples/rails-single/Procfile.dev +3 -0
- data/examples/rails-single/README.md +17 -0
- data/examples/rails-single/Rakefile +6 -0
- data/examples/rails-single/app/assets/builds/.keep +0 -0
- data/examples/rails-single/app/assets/images/.keep +0 -0
- data/examples/rails-single/app/assets/stylesheets/application.tailwind.css +1 -0
- data/examples/rails-single/app/controllers/application_controller.rb +4 -0
- data/examples/rails-single/app/controllers/concerns/.keep +0 -0
- data/examples/rails-single/app/controllers/users_controller.rb +19 -0
- data/examples/rails-single/app/helpers/application_helper.rb +2 -0
- data/examples/rails-single/app/javascript/application.js +3 -0
- data/examples/rails-single/app/javascript/controllers/application.js +9 -0
- data/examples/rails-single/app/javascript/controllers/hello_controller.js +7 -0
- data/examples/rails-single/app/javascript/controllers/index.js +8 -0
- data/examples/rails-single/app/jobs/application_job.rb +7 -0
- data/examples/rails-single/app/mailers/application_mailer.rb +4 -0
- data/examples/rails-single/app/models/application_record.rb +3 -0
- data/examples/rails-single/app/models/concerns/.keep +0 -0
- data/examples/rails-single/app/models/user.rb +2 -0
- data/examples/rails-single/app/views/layouts/application.html.erb +28 -0
- data/examples/rails-single/app/views/layouts/mailer.html.erb +13 -0
- data/examples/rails-single/app/views/layouts/mailer.text.erb +1 -0
- data/examples/rails-single/app/views/pwa/manifest.json.erb +22 -0
- data/examples/rails-single/app/views/pwa/service-worker.js +26 -0
- data/examples/rails-single/app/views/users/index.html.erb +38 -0
- data/examples/rails-single/bin/brakeman +7 -0
- data/examples/rails-single/bin/bundle +109 -0
- data/examples/rails-single/bin/dev +11 -0
- data/examples/rails-single/bin/docker-entrypoint +14 -0
- data/examples/rails-single/bin/jobs +6 -0
- data/examples/rails-single/bin/kamal +27 -0
- data/examples/rails-single/bin/rails +4 -0
- data/examples/rails-single/bin/rake +4 -0
- data/examples/rails-single/bin/rubocop +8 -0
- data/examples/rails-single/bin/setup +37 -0
- data/examples/rails-single/bin/thrust +5 -0
- data/examples/rails-single/bun.lock +224 -0
- data/examples/rails-single/config/application.rb +42 -0
- data/examples/rails-single/config/boot.rb +4 -0
- data/examples/rails-single/config/cable.yml +17 -0
- data/examples/rails-single/config/cache.yml +16 -0
- data/examples/rails-single/config/credentials.yml.enc +1 -0
- data/examples/rails-single/config/database.yml +100 -0
- data/examples/rails-single/config/environment.rb +5 -0
- data/examples/rails-single/config/environments/development.rb +69 -0
- data/examples/rails-single/config/environments/production.rb +87 -0
- data/examples/rails-single/config/environments/test.rb +50 -0
- data/examples/rails-single/config/initializers/assets.rb +7 -0
- data/examples/rails-single/config/initializers/content_security_policy.rb +25 -0
- data/examples/rails-single/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails-single/config/initializers/inflections.rb +16 -0
- data/examples/rails-single/config/locales/en.yml +31 -0
- data/examples/rails-single/config/puma.rb +41 -0
- data/examples/rails-single/config/queue.yml +18 -0
- data/examples/rails-single/config/recurring.yml +15 -0
- data/examples/rails-single/config/routes.rb +4 -0
- data/examples/rails-single/config.ru +6 -0
- data/examples/rails-single/db/cable_schema.rb +11 -0
- data/examples/rails-single/db/cache_schema.rb +12 -0
- data/examples/rails-single/db/migrate/20251123095526_create_users.rb +10 -0
- data/examples/rails-single/db/queue_schema.rb +129 -0
- data/examples/rails-single/db/seeds.rb +9 -0
- data/examples/rails-single/deploy.yml +57 -0
- data/examples/rails-single/lib/tasks/.keep +0 -0
- data/examples/rails-single/log/.keep +0 -0
- data/examples/rails-single/package.json +17 -0
- data/examples/rails-single/public/400.html +114 -0
- data/examples/rails-single/public/404.html +114 -0
- data/examples/rails-single/public/406-unsupported-browser.html +114 -0
- data/examples/rails-single/public/422.html +114 -0
- data/examples/rails-single/public/500.html +114 -0
- data/examples/rails-single/public/icon.png +0 -0
- data/examples/rails-single/public/icon.svg +3 -0
- data/examples/rails-single/public/robots.txt +1 -0
- data/examples/rails-single/script/.keep +0 -0
- data/examples/rails-single/vendor/.keep +0 -0
- data/examples/rails-single/yarn.lock +188 -0
- data/exe/nvoi +6 -0
- data/lib/nvoi/cli.rb +190 -0
- data/lib/nvoi/cloudflare/client.rb +287 -0
- data/lib/nvoi/config/config.rb +248 -0
- data/lib/nvoi/config/env_resolver.rb +63 -0
- data/lib/nvoi/config/loader.rb +102 -0
- data/lib/nvoi/config/naming.rb +196 -0
- data/lib/nvoi/config/ssh_keys.rb +82 -0
- data/lib/nvoi/config/types.rb +274 -0
- data/lib/nvoi/constants.rb +59 -0
- data/lib/nvoi/credentials/crypto.rb +88 -0
- data/lib/nvoi/credentials/editor.rb +272 -0
- data/lib/nvoi/credentials/manager.rb +173 -0
- data/lib/nvoi/deployer/cleaner.rb +36 -0
- data/lib/nvoi/deployer/image_builder.rb +23 -0
- data/lib/nvoi/deployer/infrastructure.rb +126 -0
- data/lib/nvoi/deployer/orchestrator.rb +146 -0
- data/lib/nvoi/deployer/retry.rb +67 -0
- data/lib/nvoi/deployer/service_deployer.rb +311 -0
- data/lib/nvoi/deployer/tunnel_manager.rb +57 -0
- data/lib/nvoi/deployer/types.rb +8 -0
- data/lib/nvoi/errors.rb +67 -0
- data/lib/nvoi/k8s/renderer.rb +44 -0
- data/lib/nvoi/k8s/templates.rb +29 -0
- data/lib/nvoi/logger.rb +72 -0
- data/lib/nvoi/providers/aws.rb +403 -0
- data/lib/nvoi/providers/base.rb +111 -0
- data/lib/nvoi/providers/hetzner.rb +288 -0
- data/lib/nvoi/providers/hetzner_client.rb +170 -0
- data/lib/nvoi/remote/docker_manager.rb +203 -0
- data/lib/nvoi/remote/ssh_executor.rb +72 -0
- data/lib/nvoi/remote/volume_manager.rb +103 -0
- data/lib/nvoi/service/delete.rb +234 -0
- data/lib/nvoi/service/deploy.rb +80 -0
- data/lib/nvoi/service/exec.rb +144 -0
- data/lib/nvoi/service/provider.rb +36 -0
- data/lib/nvoi/steps/application_deployer.rb +26 -0
- data/lib/nvoi/steps/database_provisioner.rb +60 -0
- data/lib/nvoi/steps/k3s_cluster_setup.rb +105 -0
- data/lib/nvoi/steps/k3s_provisioner.rb +351 -0
- data/lib/nvoi/steps/server_provisioner.rb +43 -0
- data/lib/nvoi/steps/services_provisioner.rb +29 -0
- data/lib/nvoi/steps/tunnel_configurator.rb +66 -0
- data/lib/nvoi/steps/volume_provisioner.rb +154 -0
- data/lib/nvoi/version.rb +5 -0
- data/lib/nvoi.rb +79 -0
- data/templates/app-deployment.yaml.erb +102 -0
- data/templates/app-ingress.yaml.erb +20 -0
- data/templates/app-secret.yaml.erb +10 -0
- data/templates/app-service.yaml.erb +12 -0
- data/templates/db-statefulset.yaml.erb +76 -0
- data/templates/service-deployment.yaml.erb +91 -0
- data/templates/worker-deployment.yaml.erb +50 -0
- metadata +361 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Multi-stage build for optimized image size
|
|
2
|
+
|
|
3
|
+
# Stage 1: Build
|
|
4
|
+
FROM golang:1.21-alpine AS builder
|
|
5
|
+
|
|
6
|
+
# Install build dependencies
|
|
7
|
+
RUN apk add --no-cache gcc musl-dev sqlite-dev
|
|
8
|
+
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
|
|
11
|
+
# Copy dependency files
|
|
12
|
+
COPY go.mod go.sum ./
|
|
13
|
+
|
|
14
|
+
# Download dependencies
|
|
15
|
+
RUN go mod download
|
|
16
|
+
|
|
17
|
+
# Copy source code
|
|
18
|
+
COPY . .
|
|
19
|
+
|
|
20
|
+
# Build the application
|
|
21
|
+
# CGO_ENABLED=1 is required for SQLite
|
|
22
|
+
# Set CGO_CFLAGS for musl compatibility
|
|
23
|
+
RUN CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" GOOS=linux go build -o app .
|
|
24
|
+
|
|
25
|
+
# Stage 2: Runtime
|
|
26
|
+
FROM alpine:latest
|
|
27
|
+
|
|
28
|
+
# Install runtime dependencies
|
|
29
|
+
RUN apk --no-cache add ca-certificates sqlite-libs
|
|
30
|
+
|
|
31
|
+
WORKDIR /app
|
|
32
|
+
|
|
33
|
+
# Copy binary from builder
|
|
34
|
+
COPY --from=builder /app/app .
|
|
35
|
+
|
|
36
|
+
# Create data directory
|
|
37
|
+
RUN mkdir -p /app/data
|
|
38
|
+
|
|
39
|
+
# Expose port
|
|
40
|
+
EXPOSE 3000
|
|
41
|
+
|
|
42
|
+
# Run the application
|
|
43
|
+
CMD ["./app"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Go + GORM + SQLite Example
|
|
2
|
+
|
|
3
|
+
Minimal example demonstrating NVOI deployment.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Visit `/` → Creates random user → Returns all users
|
|
8
|
+
- Visit `/health` → Health check
|
|
9
|
+
- Database persists across deployments
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
1. Own a domain and add it to Cloudflare DNS
|
|
14
|
+
2. Hetzner Cloud account
|
|
15
|
+
3. Run `make build` from project root
|
|
16
|
+
|
|
17
|
+
## Deploy
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Setup
|
|
21
|
+
cp .env.example .env
|
|
22
|
+
vim .env # Add your tokens
|
|
23
|
+
|
|
24
|
+
# Update domain in deploy.yml
|
|
25
|
+
vim deploy.yml # Set domain/subdomain
|
|
26
|
+
|
|
27
|
+
# Deploy
|
|
28
|
+
make example-deploy
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Test
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# After deployment
|
|
35
|
+
curl https://yoursubdomain.yourdomain.com/
|
|
36
|
+
curl https://yoursubdomain.yourdomain.com/health
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Local Test
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
make example-run
|
|
43
|
+
curl http://localhost:3000/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Response Example
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"message": "User created on this visit!",
|
|
51
|
+
"new_user": {
|
|
52
|
+
"id": 1,
|
|
53
|
+
"name": "Alice Smith",
|
|
54
|
+
"email": "user1699999999@example.com"
|
|
55
|
+
},
|
|
56
|
+
"total_users": 1,
|
|
57
|
+
"all_users": [...]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
application:
|
|
2
|
+
name: example-golang
|
|
3
|
+
environment: production
|
|
4
|
+
|
|
5
|
+
# Provider configuration
|
|
6
|
+
domain_provider:
|
|
7
|
+
cloudflare:
|
|
8
|
+
api_token: $CLOUDFLARE_API_TOKEN
|
|
9
|
+
account_id: $CLOUDFLARE_ACCOUNT_ID
|
|
10
|
+
|
|
11
|
+
compute_provider:
|
|
12
|
+
hetzner:
|
|
13
|
+
api_token: $HETZNER_API_TOKEN
|
|
14
|
+
server_type: cx22 # Explicit server type
|
|
15
|
+
server_location: fsn1 # Explicit location
|
|
16
|
+
|
|
17
|
+
# Server configuration (single server, master: true is implicit)
|
|
18
|
+
servers:
|
|
19
|
+
master:
|
|
20
|
+
type: cx22
|
|
21
|
+
location: fsn1
|
|
22
|
+
|
|
23
|
+
# Container retention
|
|
24
|
+
keep_count: 2
|
|
25
|
+
|
|
26
|
+
app:
|
|
27
|
+
web:
|
|
28
|
+
servers: [master]
|
|
29
|
+
domain: rb.run # Your domain (must be on Cloudflare)
|
|
30
|
+
subdomain: golang # Subdomain for this app
|
|
31
|
+
port: 3000
|
|
32
|
+
|
|
33
|
+
# Nested health check configuration
|
|
34
|
+
healthcheck:
|
|
35
|
+
type: http
|
|
36
|
+
path: /health
|
|
37
|
+
port: 3000
|
|
38
|
+
interval: 10s
|
|
39
|
+
timeout: 5s
|
|
40
|
+
retries: 3
|
|
41
|
+
|
|
42
|
+
# Volume mounts for SQLite data persistence
|
|
43
|
+
volumes:
|
|
44
|
+
data: /app/data
|
|
45
|
+
|
|
46
|
+
# SQLite database (adapter specified, volume configured at service level)
|
|
47
|
+
database:
|
|
48
|
+
servers: [master]
|
|
49
|
+
adapter: sqlite3
|
|
50
|
+
|
|
51
|
+
env:
|
|
52
|
+
APP_NAME: nvoi-example-app
|
|
53
|
+
LOG_LEVEL: info
|
|
54
|
+
GIN_MODE: release
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module github.com/nvoi/example-app
|
|
2
|
+
|
|
3
|
+
go 1.21
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
github.com/gin-gonic/gin v1.9.1
|
|
7
|
+
gorm.io/driver/sqlite v1.5.4
|
|
8
|
+
gorm.io/gorm v1.25.5
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
require (
|
|
12
|
+
github.com/bytedance/sonic v1.9.1 // indirect
|
|
13
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
|
14
|
+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
|
15
|
+
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
16
|
+
github.com/go-playground/locales v0.14.1 // indirect
|
|
17
|
+
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
18
|
+
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
|
19
|
+
github.com/goccy/go-json v0.10.2 // indirect
|
|
20
|
+
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
21
|
+
github.com/jinzhu/now v1.1.5 // indirect
|
|
22
|
+
github.com/json-iterator/go v1.1.12 // indirect
|
|
23
|
+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
|
24
|
+
github.com/leodido/go-urn v1.2.4 // indirect
|
|
25
|
+
github.com/mattn/go-isatty v0.0.19 // indirect
|
|
26
|
+
github.com/mattn/go-sqlite3 v1.14.18 // indirect
|
|
27
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
28
|
+
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
29
|
+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
|
30
|
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
31
|
+
github.com/ugorji/go/codec v1.2.11 // indirect
|
|
32
|
+
golang.org/x/arch v0.3.0 // indirect
|
|
33
|
+
golang.org/x/crypto v0.9.0 // indirect
|
|
34
|
+
golang.org/x/net v0.10.0 // indirect
|
|
35
|
+
golang.org/x/sys v0.8.0 // indirect
|
|
36
|
+
golang.org/x/text v0.9.0 // indirect
|
|
37
|
+
google.golang.org/protobuf v1.30.0 // indirect
|
|
38
|
+
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
39
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
|
2
|
+
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
|
3
|
+
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
|
4
|
+
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
|
5
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
|
6
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
|
7
|
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
8
|
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
9
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
10
|
+
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
|
11
|
+
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
|
12
|
+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
13
|
+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
14
|
+
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
|
15
|
+
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
|
16
|
+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
17
|
+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
18
|
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
19
|
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
20
|
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
21
|
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
22
|
+
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
|
23
|
+
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
24
|
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
25
|
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
26
|
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
27
|
+
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
28
|
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
29
|
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
30
|
+
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
31
|
+
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
32
|
+
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
33
|
+
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
34
|
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
35
|
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
36
|
+
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
37
|
+
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
|
38
|
+
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
|
39
|
+
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
|
40
|
+
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
|
41
|
+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
42
|
+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
43
|
+
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
|
44
|
+
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
45
|
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
46
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
47
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
48
|
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
49
|
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
50
|
+
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
|
51
|
+
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
|
52
|
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
53
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
54
|
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
55
|
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
56
|
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
57
|
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
58
|
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
59
|
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
60
|
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
61
|
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
62
|
+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
63
|
+
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
64
|
+
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
65
|
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
66
|
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
67
|
+
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
68
|
+
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
69
|
+
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
70
|
+
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
|
71
|
+
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
72
|
+
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
|
73
|
+
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
|
74
|
+
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
75
|
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
76
|
+
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
77
|
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
78
|
+
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|
79
|
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
80
|
+
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
81
|
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
82
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
83
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
84
|
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
85
|
+
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
|
86
|
+
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
87
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
88
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
89
|
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
90
|
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
91
|
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
92
|
+
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
|
93
|
+
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
|
94
|
+
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
|
95
|
+
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
|
96
|
+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
"github.com/nvoi/example-app/models"
|
|
16
|
+
"gorm.io/driver/sqlite"
|
|
17
|
+
"gorm.io/gorm"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
var db *gorm.DB
|
|
21
|
+
|
|
22
|
+
func main() {
|
|
23
|
+
// Initialize database
|
|
24
|
+
var err error
|
|
25
|
+
db, err = initDB()
|
|
26
|
+
if err != nil {
|
|
27
|
+
log.Fatalf("Failed to initialize database: %v", err)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Auto-migrate database schema
|
|
31
|
+
if err := db.AutoMigrate(&models.User{}); err != nil {
|
|
32
|
+
log.Fatalf("Failed to migrate database: %v", err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Setup router
|
|
36
|
+
router := setupRouter()
|
|
37
|
+
|
|
38
|
+
// Configure server
|
|
39
|
+
srv := &http.Server{
|
|
40
|
+
Addr: ":3000",
|
|
41
|
+
Handler: router,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Start server in goroutine
|
|
45
|
+
go func() {
|
|
46
|
+
log.Println("Starting server on :3000")
|
|
47
|
+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
48
|
+
log.Fatalf("Server failed to start: %v", err)
|
|
49
|
+
}
|
|
50
|
+
}()
|
|
51
|
+
|
|
52
|
+
// Wait for interrupt signal for graceful shutdown
|
|
53
|
+
quit := make(chan os.Signal, 1)
|
|
54
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
55
|
+
<-quit
|
|
56
|
+
|
|
57
|
+
log.Println("Shutting down server...")
|
|
58
|
+
|
|
59
|
+
// Graceful shutdown with 5 second timeout
|
|
60
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
61
|
+
defer cancel()
|
|
62
|
+
|
|
63
|
+
if err := srv.Shutdown(ctx); err != nil {
|
|
64
|
+
log.Fatalf("Server forced to shutdown: %v", err)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.Println("Server exited")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func initDB() (*gorm.DB, error) {
|
|
71
|
+
// Ensure data directory exists
|
|
72
|
+
if err := os.MkdirAll("data", 0755); err != nil {
|
|
73
|
+
return nil, fmt.Errorf("failed to create data directory: %w", err)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Open SQLite database
|
|
77
|
+
db, err := gorm.Open(sqlite.Open("data/app.db"), &gorm.Config{})
|
|
78
|
+
if err != nil {
|
|
79
|
+
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
log.Println("Database connected successfully")
|
|
83
|
+
return db, nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func setupRouter() *gin.Engine {
|
|
87
|
+
// Set Gin mode from environment
|
|
88
|
+
if mode := os.Getenv("GIN_MODE"); mode != "" {
|
|
89
|
+
gin.SetMode(mode)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
router := gin.Default()
|
|
93
|
+
|
|
94
|
+
// Health check endpoint (required for deployment)
|
|
95
|
+
router.GET("/health", healthCheck)
|
|
96
|
+
|
|
97
|
+
// Main endpoint: creates user on every visit, returns all users
|
|
98
|
+
router.GET("/", handleVisit)
|
|
99
|
+
|
|
100
|
+
return router
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Health check handler (required for zero-downtime deployments)
|
|
104
|
+
func healthCheck(c *gin.Context) {
|
|
105
|
+
// Check database connectivity
|
|
106
|
+
sqlDB, err := db.DB()
|
|
107
|
+
if err != nil {
|
|
108
|
+
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
109
|
+
"status": "unhealthy",
|
|
110
|
+
"error": "database connection failed",
|
|
111
|
+
})
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if err := sqlDB.Ping(); err != nil {
|
|
116
|
+
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
117
|
+
"status": "unhealthy",
|
|
118
|
+
"error": "database ping failed",
|
|
119
|
+
})
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
c.JSON(http.StatusOK, gin.H{
|
|
124
|
+
"status": "healthy",
|
|
125
|
+
"service": "nvoi-example-app",
|
|
126
|
+
"time": time.Now().Format(time.RFC3339),
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Main handler: creates a new user on every visit and returns all users
|
|
131
|
+
func handleVisit(c *gin.Context) {
|
|
132
|
+
// Create a new user with random name
|
|
133
|
+
newUser := models.User{
|
|
134
|
+
Name: generateRandomName(),
|
|
135
|
+
Email: fmt.Sprintf("user-xxx%d@example.com", time.Now().UnixNano()),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if err := db.Create(&newUser).Error; err != nil {
|
|
139
|
+
c.JSON(http.StatusInternalServerError, gin.H{
|
|
140
|
+
"error": "Failed to create user",
|
|
141
|
+
})
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fetch all users
|
|
146
|
+
var users []models.User
|
|
147
|
+
if err := db.Find(&users).Error; err != nil {
|
|
148
|
+
c.JSON(http.StatusInternalServerError, gin.H{
|
|
149
|
+
"error": "Failed to fetch users",
|
|
150
|
+
})
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Get deployment info
|
|
155
|
+
hostname, _ := os.Hostname()
|
|
156
|
+
|
|
157
|
+
// Return response
|
|
158
|
+
c.JSON(http.StatusOK, gin.H{
|
|
159
|
+
"hostname": hostname,
|
|
160
|
+
"message": "User created on this visit!",
|
|
161
|
+
"new_user": newUser,
|
|
162
|
+
"total_users": len(users),
|
|
163
|
+
"all_users": users,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Generate random name for demo purposes
|
|
168
|
+
func generateRandomName() string {
|
|
169
|
+
firstNames := []string{"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack"}
|
|
170
|
+
lastNames := []string{"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"}
|
|
171
|
+
|
|
172
|
+
rand.Seed(time.Now().UnixNano())
|
|
173
|
+
firstName := firstNames[rand.Intn(len(firstNames))]
|
|
174
|
+
lastName := lastNames[rand.Intn(len(lastNames))]
|
|
175
|
+
|
|
176
|
+
return fmt.Sprintf("%s %s", firstName, lastName)
|
|
177
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package models
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
"gorm.io/gorm"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// User represents a user in the system
|
|
10
|
+
type User struct {
|
|
11
|
+
ID uint `gorm:"primarykey" json:"id"`
|
|
12
|
+
Name string `gorm:"not null" json:"name" binding:"required"`
|
|
13
|
+
Email string `gorm:"uniqueIndex;not null" json:"email" binding:"required,email"`
|
|
14
|
+
CreatedAt time.Time `json:"created_at"`
|
|
15
|
+
UpdatedAt time.Time `json:"updated_at"`
|
|
16
|
+
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Multi-stage build for optimized image size
|
|
2
|
+
|
|
3
|
+
# Stage 1: Build
|
|
4
|
+
FROM golang:1.23-alpine AS builder
|
|
5
|
+
|
|
6
|
+
# Install build dependencies (no SQLite needed for Postgres)
|
|
7
|
+
RUN apk add --no-cache gcc musl-dev
|
|
8
|
+
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
|
|
11
|
+
# Copy dependency files
|
|
12
|
+
COPY go.mod go.sum ./
|
|
13
|
+
|
|
14
|
+
# Download dependencies
|
|
15
|
+
RUN go mod download
|
|
16
|
+
|
|
17
|
+
# Copy source code
|
|
18
|
+
COPY . .
|
|
19
|
+
|
|
20
|
+
# Build the application
|
|
21
|
+
# CGO_ENABLED=1 required for some postgres driver features
|
|
22
|
+
RUN CGO_ENABLED=1 GOOS=linux go build -o app .
|
|
23
|
+
|
|
24
|
+
# Stage 2: Runtime
|
|
25
|
+
FROM alpine:latest
|
|
26
|
+
|
|
27
|
+
# Install runtime dependencies
|
|
28
|
+
RUN apk --no-cache add ca-certificates
|
|
29
|
+
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
|
|
32
|
+
# Copy binary from builder
|
|
33
|
+
COPY --from=builder /app/app .
|
|
34
|
+
|
|
35
|
+
# Expose port
|
|
36
|
+
EXPOSE 3000
|
|
37
|
+
|
|
38
|
+
# Run the application
|
|
39
|
+
CMD ["./app"]
|