feat: add Docker deployment and application configuration
Add package declarations across the Go scaffold, YAML configuration loading and validation, and a minimal API entry point. Add development and production Compose files, a multi-stage Docker image, configuration template, and Make targets. Remove the directory scaffolding script.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
.git
|
||||
.gitignore
|
||||
.idea
|
||||
.env
|
||||
.env.*
|
||||
graphify-out
|
||||
developer_doc
|
||||
deploy
|
||||
tmp
|
||||
*.log
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
FROM golang:1.27-alpine AS build
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-trimpath \
|
||||
-ldflags="-s -w" \
|
||||
-o /out/robot-market-api \
|
||||
./cmd/api
|
||||
|
||||
FROM alpine:3.22 AS production
|
||||
|
||||
RUN apk add --no-cache ca-certificates \
|
||||
&& addgroup -S app \
|
||||
&& adduser -S -G app app
|
||||
|
||||
COPY --from=build /out/robot-market-api /usr/local/bin/robot-market-api
|
||||
|
||||
USER app
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/robot-market-api"]
|
||||
@@ -0,0 +1,10 @@
|
||||
.PHONY: dev dev-down docker-build
|
||||
|
||||
dev:
|
||||
docker compose -f deploy/docker-compose.dev.yml up --build
|
||||
|
||||
dev-down:
|
||||
docker compose -f deploy/docker-compose.dev.yml down
|
||||
|
||||
docker-build:
|
||||
docker compose -f deploy/docker-compose.prod.yml build
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.my-rm.com/Robot-Market/shop-backend/internal/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if _, err := app.LoadConfig(""); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
environment: development
|
||||
|
||||
http:
|
||||
address: 0.0.0.0:8080
|
||||
|
||||
# The database is managed outside Docker Compose.
|
||||
database:
|
||||
url: postgres://robot_market:change-me@host.docker.internal:5432/robot_market?sslmode=disable
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "Creating Robot Market Phase 1 backend structure..."
|
||||
|
||||
# Directories
|
||||
directories=(
|
||||
# Applications
|
||||
"cmd/api"
|
||||
"cmd/cli"
|
||||
|
||||
# Application bootstrap
|
||||
"internal/app"
|
||||
|
||||
# CLI commands
|
||||
"internal/command"
|
||||
|
||||
# Domain features
|
||||
"internal/user"
|
||||
"internal/auth"
|
||||
"internal/catalog"
|
||||
|
||||
# HTTP layer
|
||||
"internal/http/handler"
|
||||
"internal/http/route"
|
||||
"internal/http/middleware"
|
||||
|
||||
# Database layer
|
||||
"internal/postgres/query"
|
||||
"internal/postgres/sqlc"
|
||||
|
||||
# Shared packages
|
||||
"pkg/apperr"
|
||||
"pkg/log"
|
||||
|
||||
# Integration tests
|
||||
"integration/auth"
|
||||
"integration/catalog"
|
||||
"integration/testutil"
|
||||
|
||||
# Database migrations
|
||||
"migrations"
|
||||
|
||||
# Scripts
|
||||
"scripts"
|
||||
)
|
||||
|
||||
for dir in "${directories[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
done
|
||||
|
||||
|
||||
# Placeholder files
|
||||
files=(
|
||||
# API
|
||||
"cmd/api/main.go"
|
||||
|
||||
# CLI
|
||||
"cmd/cli/main.go"
|
||||
|
||||
# App
|
||||
"internal/app/app.go"
|
||||
"internal/app/config.go"
|
||||
|
||||
# Commands
|
||||
"internal/command/root.go"
|
||||
"internal/command/admin.go"
|
||||
"internal/command/seed.go"
|
||||
|
||||
# User
|
||||
"internal/user/domain.go"
|
||||
"internal/user/repository.go"
|
||||
"internal/user/errors.go"
|
||||
|
||||
# Auth
|
||||
"internal/auth/dto.go"
|
||||
"internal/auth/service.go"
|
||||
"internal/auth/errors.go"
|
||||
|
||||
# Catalog
|
||||
"internal/catalog/domain.go"
|
||||
"internal/catalog/dto.go"
|
||||
"internal/catalog/service.go"
|
||||
"internal/catalog/repository.go"
|
||||
"internal/catalog/errors.go"
|
||||
|
||||
# HTTP
|
||||
"internal/http/server.go"
|
||||
"internal/http/config.go"
|
||||
|
||||
# PostgreSQL
|
||||
"internal/postgres/db.go"
|
||||
"internal/postgres/user.go"
|
||||
"internal/postgres/auth.go"
|
||||
"internal/postgres/catalog.go"
|
||||
|
||||
# sqlc query placeholders
|
||||
"internal/postgres/query/user.sql"
|
||||
"internal/postgres/query/auth.sql"
|
||||
"internal/postgres/query/catalog.sql"
|
||||
|
||||
# Shared packages
|
||||
"pkg/apperr/error.go"
|
||||
"pkg/log/log.go"
|
||||
|
||||
# Integration tests
|
||||
"integration/auth/service_test.go"
|
||||
"integration/catalog/service_test.go"
|
||||
|
||||
"integration/testutil/postgres.go"
|
||||
"integration/testutil/migrations.go"
|
||||
"integration/testutil/fixtures.go"
|
||||
|
||||
# Root files
|
||||
"sqlc.yaml"
|
||||
"Makefile"
|
||||
"go.mod"
|
||||
)
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
touch "$file"
|
||||
done
|
||||
|
||||
|
||||
echo "Phase 1 structure created successfully."
|
||||
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
image: robot-market-backend:dev
|
||||
environment:
|
||||
CONFIG_FILE: /etc/robot-market/config.yaml
|
||||
volumes:
|
||||
- ../configs/config.example.yaml:/etc/robot-market/config.yaml:ro
|
||||
ports:
|
||||
- "${APP_PORT:-8080}:8080"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
image: ${ROBOT_MARKET_IMAGE:-robot-market-backend:latest}
|
||||
environment:
|
||||
CONFIG_FILE: /etc/robot-market/config.yaml
|
||||
volumes:
|
||||
- ${CONFIG_FILE:-../configs/config.example.yaml}:/etc/robot-market/config.yaml:ro
|
||||
ports:
|
||||
- "${APP_PORT:-8080}:8080"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
@@ -1,3 +1,5 @@
|
||||
module git.my-rm.com/Robot-Market/shop-backend
|
||||
|
||||
go 1.27.1
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1 @@
|
||||
package auth_test
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog_test
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package testutil
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package testutil
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package testutil
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package app
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const defaultConfigPath = "configs/config.example.yaml"
|
||||
|
||||
// Config contains the settings required to start the application.
|
||||
type Config struct {
|
||||
Environment string `yaml:"environment"`
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Address string `yaml:"address"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// LoadConfig reads configuration from YAML, applies environment overrides,
|
||||
// and validates the result. An empty path uses CONFIG_FILE when set.
|
||||
func LoadConfig(path string) (Config, error) {
|
||||
if path == "" {
|
||||
path = os.Getenv("CONFIG_FILE")
|
||||
}
|
||||
if path == "" {
|
||||
path = defaultConfigPath
|
||||
}
|
||||
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("read config %q: %w", path, err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(contents, &config); err != nil {
|
||||
return Config{}, fmt.Errorf("parse config %q: %w", path, err)
|
||||
}
|
||||
|
||||
applyEnvironment(&config)
|
||||
if err := config.Validate(); err != nil {
|
||||
return Config{}, fmt.Errorf("validate config: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (config Config) Validate() error {
|
||||
var missing []string
|
||||
if strings.TrimSpace(config.Environment) == "" {
|
||||
missing = append(missing, "environment")
|
||||
}
|
||||
if strings.TrimSpace(config.HTTP.Address) == "" {
|
||||
missing = append(missing, "http.address")
|
||||
}
|
||||
if strings.TrimSpace(config.Database.URL) == "" {
|
||||
missing = append(missing, "database.url")
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return errors.New("missing required values: " + strings.Join(missing, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyEnvironment(config *Config) {
|
||||
if value := os.Getenv("APP_ENVIRONMENT"); value != "" {
|
||||
config.Environment = value
|
||||
}
|
||||
if value := os.Getenv("APP_HTTP_ADDRESS"); value != "" {
|
||||
config.HTTP.Address = value
|
||||
}
|
||||
if value := os.Getenv("APP_DATABASE_URL"); value != "" {
|
||||
config.Database.URL = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package auth
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package auth
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package auth
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package catalog
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package command
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package command
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package command
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package http
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package http
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package postgres
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package postgres
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package postgres
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package postgres
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package user
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package user
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package user
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package apperr
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package log
|
||||
|
||||
Reference in New Issue
Block a user