tsk-6: Added docker support (#14)
Closes #6 Reviewed-on: phoenix/textsender-api#14 Co-authored-by: phoenix <kundeng00@pm.me> Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
vendor/
|
||||||
|
|
||||||
|
# Ignore git directory
|
||||||
|
.git/
|
||||||
|
|
||||||
|
.gitea/
|
||||||
|
|
||||||
|
# Ignore environment files (configure via docker-compose instead)
|
||||||
|
.env*
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
JWT_SECRET=NULqYIzgt28bTiyziCd7IOO7b6LnWDW!
|
||||||
|
DB_NAME=textsender_db
|
||||||
|
DB_USER=textsender
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=main_db
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_SSLMODE=disable
|
||||||
@@ -3,3 +3,4 @@
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
|
.env.docker
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
# Multi-stage Dockerfile for Go application
|
||||||
|
FROM golang:1.25.3 AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
openssh-client git
|
||||||
|
|
||||||
|
RUN mkdir -p -m 0700 ~/.ssh && \
|
||||||
|
ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
# Configure Git to use SSH for GitHub
|
||||||
|
RUN git config --global url."ssh://git@git.kundeng.us".insteadOf "https://git.kundeng.us"
|
||||||
|
|
||||||
|
# Set up the Go environment for private modules
|
||||||
|
ENV GOPRIVATE=git.kundeng.us
|
||||||
|
|
||||||
|
|
||||||
|
# Copy go mod and sum files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
RUN --mount=type=ssh mkdir src && \
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY ./cmd ./cmd
|
||||||
|
COPY ./internal ./internal
|
||||||
|
COPY ./Makefile .
|
||||||
|
COPY ./.env .
|
||||||
|
COPY ./migrations ./migrations
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux make build
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM alpine:latest AS production
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
# Copy the pre-built binary file from the previous stage
|
||||||
|
COPY --from=builder /app/textsender-api .
|
||||||
|
COPY --from=builder /app/.env ./
|
||||||
|
COPY --from=builder /app/migrations ./migrations
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Command to run the executable
|
||||||
|
CMD ["./textsender-api"]
|
||||||
+15
-5
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-api/internal/config"
|
"git.kundeng.us/phoenix/textsender-api/internal/config"
|
||||||
"git.kundeng.us/phoenix/textsender-api/internal/db"
|
database "git.kundeng.us/phoenix/textsender-api/internal/db"
|
||||||
"git.kundeng.us/phoenix/textsender-api/internal/handler"
|
"git.kundeng.us/phoenix/textsender-api/internal/handler"
|
||||||
"git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint"
|
"git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint"
|
||||||
mdlware "git.kundeng.us/phoenix/textsender-api/internal/middleware"
|
mdlware "git.kundeng.us/phoenix/textsender-api/internal/middleware"
|
||||||
@@ -32,26 +32,36 @@ func main() {
|
|||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
database, err := db.NewDatabase(cfg.GetDBConnString())
|
db, err := database.NewDatabase(cfg.GetDBConnString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to connect to database: %v", err)
|
log.Fatalf("Failed to connect to database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
defer db.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if cfg.ResetDB {
|
if cfg.ResetDB {
|
||||||
log.Println("Resetting database")
|
log.Println("Resetting database")
|
||||||
if err := database.ResetDatabase(ctx); err != nil {
|
if err := db.ResetDatabase(ctx); err != nil {
|
||||||
log.Fatalf("Failed to reset database: %v", err)
|
log.Fatalf("Failed to reset database: %v", err)
|
||||||
}
|
}
|
||||||
log.Println("Database reset completed. Exiting.")
|
log.Println("Database reset completed. Exiting.")
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
if exists, err := database.TableExists(ctx, db.Pool, "contacts "); err == nil && !exists {
|
||||||
|
fmt.Println("Resetting database")
|
||||||
|
err = db.ResetDatabase(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error:%v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Error:%v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtService := services.NewJWTService(cfg.JWTSecret)
|
jwtService := services.NewJWTService(cfg.JWTSecret)
|
||||||
|
|
||||||
contactStore := store.NewContactStore(database.Pool)
|
contactStore := store.NewContactStore(db.Pool)
|
||||||
contactHandler := handler.NewContactHandler(contactStore)
|
contactHandler := handler.NewContactHandler(contactStore)
|
||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
version: '3.8' # Use a recent version
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: # Tells docker-compose to build the Dockerfile in the current directory
|
||||||
|
context: .
|
||||||
|
ssh: ["default"] # Uses host's SSH agent
|
||||||
|
container_name: textsender # Optional: Give the container a specific name
|
||||||
|
ports:
|
||||||
|
# Map host port 8000 to container port 3000 (adjust as needed)
|
||||||
|
- "8080:8080"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
main_db:
|
||||||
|
condition: service_healthy # Wait for the DB to be healthy before starting the app
|
||||||
|
networks:
|
||||||
|
- textsender-network
|
||||||
|
restart: unless-stopped # Optional: Restart policy
|
||||||
|
|
||||||
|
auth_api:
|
||||||
|
build: # Tells docker-compose to build the Dockerfile in the current directory
|
||||||
|
context: ../textsender-auth
|
||||||
|
ssh: ["default"] # Uses host's SSH agent
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: textsender_auth # Optional: Give the container a specific name
|
||||||
|
ports:
|
||||||
|
# Map host port 8000 to container port 3000 (adjust as needed)
|
||||||
|
- "9080:9080"
|
||||||
|
env_file:
|
||||||
|
- ../textsender-auth/.env
|
||||||
|
depends_on:
|
||||||
|
auth_db:
|
||||||
|
condition: service_healthy # Wait for the DB to be healthy before starting the app
|
||||||
|
networks:
|
||||||
|
- textsender-network
|
||||||
|
restart: unless-stopped # Optional: Restart policy
|
||||||
|
|
||||||
|
# PostgreSQL Database Service
|
||||||
|
main_db:
|
||||||
|
image: postgres:18.0-alpine # Use an official Postgres image (Alpine variant is smaller)
|
||||||
|
container_name: textsender_db # Optional: Give the container a specific name
|
||||||
|
environment:
|
||||||
|
# These MUST match the user, password, and database name in the DATABASE_URL above
|
||||||
|
POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password}
|
||||||
|
POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_db}
|
||||||
|
volumes:
|
||||||
|
# Persist database data using a named volume
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
healthcheck:
|
||||||
|
# Checks if Postgres is ready to accept connections
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- textsender-network
|
||||||
|
restart: always # Optional: Restart policy
|
||||||
|
|
||||||
|
auth_db:
|
||||||
|
image: postgres:18.0-alpine # Use an official Postgres image (Alpine variant is smaller)
|
||||||
|
container_name: textsender_auth_db # Optional: Give the container a specific name
|
||||||
|
environment:
|
||||||
|
# These MUST match the user, password, and database name in the DATABASE_URL above
|
||||||
|
POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_auth}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password}
|
||||||
|
POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_auth_db}
|
||||||
|
volumes:
|
||||||
|
# Persist database data using a named volume
|
||||||
|
- postgres_data_auth:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
healthcheck:
|
||||||
|
# Checks if Postgres is ready to accept connections
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- textsender-network
|
||||||
|
restart: always # Optional: Restart policy
|
||||||
|
|
||||||
|
# Define the named volume for data persistence
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
driver: local # Use the default local driver
|
||||||
|
postgres_data_auth:
|
||||||
|
driver: local # Use the default local driver
|
||||||
|
|
||||||
|
networks:
|
||||||
|
textsender-network:
|
||||||
|
driver: bridge
|
||||||
@@ -54,6 +54,25 @@ func NewDatabase(connString string) (*Database, error) {
|
|||||||
return &Database{Pool: pool}, nil
|
return &Database{Pool: pool}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TableExists(ctx context.Context, conn *pgxpool.Pool, tableName string) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
query := `
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = $1
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
err := conn.QueryRow(ctx, query, tableName).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error checking if table exists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Database) Close() {
|
func (db *Database) Close() {
|
||||||
if db.Pool != nil {
|
if db.Pool != nil {
|
||||||
db.Pool.Close()
|
db.Pool.Close()
|
||||||
@@ -115,4 +134,3 @@ func (db *Database) ResetDatabase(ctx context.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user