Skip to content

Commit a392610

Browse files
FEATURE (docker): Build project for sending to Docker hub
1 parent 6725adf commit a392610

File tree

14 files changed

+220
-103
lines changed

14 files changed

+220
-103
lines changed

.env.example

Lines changed: 0 additions & 4 deletions
This file was deleted.

.github/workflows/docker.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Build & push Docker image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch: {}
7+
8+
jobs:
9+
docker:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Check out code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up QEMU (enables multi-arch emulation)
17+
uses: docker/setup-qemu-action@v3
18+
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v3
21+
22+
- name: Log in to Docker Hub
23+
uses: docker/login-action@v3
24+
with:
25+
username: ${{ secrets.DOCKERHUB_USERNAME }}
26+
password: ${{ secrets.DOCKERHUB_TOKEN }}
27+
28+
- name: Build and push
29+
uses: docker/build-push-action@v5
30+
with:
31+
context: .
32+
push: true
33+
platforms: linux/amd64,linux/arm64 # both chip families
34+
tags: |
35+
rotislavdugin/postgresus:latest
36+
rotislavdugin/postgresus:${{ github.sha }}
37+
# If you want build-time args (TARGETOS etc.):
38+
# build-args: |
39+
# TARGETOS=linux

Dockerfile

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# ========= BUILD FRONTEND =========
2+
FROM --platform=$BUILDPLATFORM node:24-alpine AS frontend-build
3+
4+
WORKDIR /frontend
5+
6+
COPY frontend/package.json frontend/package-lock.json ./
7+
RUN npm ci
8+
COPY frontend/ ./
9+
10+
# Copy .env file (with fallback to .env.production.example)
11+
RUN if [ ! -f .env ]; then \
12+
if [ -f .env.production.example ]; then \
13+
cp .env.production.example .env; \
14+
fi; \
15+
fi
16+
17+
RUN npm run build
18+
19+
# ========= BUILD BACKEND =========
20+
FROM --platform=$BUILDPLATFORM golang:1.23.3 AS backend-build
21+
22+
# Install Go public tools needed in runtime
23+
RUN curl -fsSL https://raw.githubusercontent.com/pressly/goose/master/install.sh | sh
24+
RUN go install github.com/swaggo/swag/cmd/swag@latest
25+
26+
# Set working directory
27+
WORKDIR /app
28+
29+
# Install Go dependencies
30+
COPY backend/go.mod backend/go.sum ./
31+
RUN go mod download
32+
33+
# Create required directories for embedding
34+
RUN mkdir -p /app/ui/build
35+
36+
# Copy frontend build output for embedding
37+
COPY --from=frontend-build /frontend/dist /app/ui/build
38+
39+
# Generate Swagger documentation
40+
COPY backend/ ./
41+
RUN swag init -d . -g cmd/main.go -o swagger
42+
43+
# Compile the backend
44+
ARG TARGETOS
45+
ARG TARGETARCH
46+
ARG TARGETVARIANT
47+
RUN CGO_ENABLED=0 \
48+
GOOS=$TARGETOS \
49+
GOARCH=$TARGETARCH \
50+
go build -o /app/main ./cmd/main.go
51+
52+
53+
# ========= RUNTIME =========
54+
FROM --platform=$TARGETPLATFORM debian:bookworm-slim
55+
56+
# Install PostgreSQL client tools (versions 13-17)
57+
RUN apt-get update && apt-get install -y --no-install-recommends \
58+
wget ca-certificates gnupg lsb-release && \
59+
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
60+
echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
61+
> /etc/apt/sources.list.d/pgdg.list && \
62+
apt-get update && \
63+
apt-get install -y --no-install-recommends \
64+
postgresql-client-13 postgresql-client-14 postgresql-client-15 \
65+
postgresql-client-16 postgresql-client-17 && \
66+
rm -rf /var/lib/apt/lists/*
67+
68+
# Create symlinks for PostgreSQL client tools
69+
RUN for v in 13 14 15 16 17; do \
70+
mkdir -p /usr/pgsql-$v/bin && \
71+
for b in pg_dump psql pg_restore createdb dropdb; do \
72+
ln -sf /usr/bin/$b /usr/pgsql-$v/bin/$b; \
73+
done; \
74+
done
75+
76+
WORKDIR /app
77+
78+
# Copy Goose from build stage
79+
COPY --from=backend-build /usr/local/bin/goose /usr/local/bin/goose
80+
81+
# Copy app binary
82+
COPY --from=backend-build /app/main .
83+
84+
# Copy migrations directory
85+
COPY backend/migrations ./migrations
86+
87+
# Copy UI files
88+
COPY --from=backend-build /app/ui/build ./ui/build
89+
90+
# Copy .env file (with fallback to .env.production.example)
91+
COPY backend/.env* /app/
92+
RUN if [ ! -f /app/.env ]; then \
93+
if [ -f /app/.env.production.example ]; then \
94+
cp /app/.env.production.example /app/.env; \
95+
fi; \
96+
fi
97+
98+
EXPOSE 4005
99+
100+
CMD ["./main"]

backend/.env.development.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# docker-compose.yml
2+
DEV_DB_NAME=postgresus
3+
DEV_DB_USERNAME=postgres
4+
DEV_DB_PASSWORD=Q1234567
5+
#app
6+
ENV_MODE=development
7+
# db
8+
DATABASE_DSN=host=dev-db user=postgres password=Q1234567 dbname=postgresus port=5437 sslmode=disable
9+
DATABASE_URL=postgres://postgres:Q1234567@dev-db:5437/postgresus?sslmode=disable
10+
# migrations
11+
GOOSE_DRIVER=postgres
12+
GOOSE_DBSTRING=postgres://postgres:Q1234567@dev-db:5437/postgresus?sslmode=disable
13+
GOOSE_MIGRATION_DIR=./migrations

backend/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ swagger/*
99
swagger/docs.go
1010
swagger/swagger.json
1111
swagger/swagger.yaml
12-
postgresus-backend.exe
12+
postgresus-backend.exe
13+
ui/build/*

backend/Dockerfile

Lines changed: 0 additions & 44 deletions
This file was deleted.

backend/cmd/main.go

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package main
33
import (
44
"context"
55
"log/slog"
6+
"net/http"
67
"os"
78
"os/exec"
89
"os/signal"
10+
"path/filepath"
911
"syscall"
1012
"time"
1113

12-
"net/http"
13-
1414
"postgresus-backend/internal/config"
1515
"postgresus-backend/internal/downdetect"
1616
"postgresus-backend/internal/features/backups"
@@ -51,30 +51,11 @@ func main() {
5151
gin.SetMode(gin.ReleaseMode)
5252
ginApp := gin.Default()
5353

54-
// Setup CORS
55-
ginApp.Use(cors.New(cors.Config{
56-
AllowOrigins: []string{"*"},
57-
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
58-
AllowHeaders: []string{
59-
"Origin",
60-
"Content-Length",
61-
"Content-Type",
62-
"Authorization",
63-
"Accept",
64-
"Accept-Language",
65-
"Accept-Encoding",
66-
"Access-Control-Request-Method",
67-
"Access-Control-Request-Headers",
68-
"Access-Control-Allow-Methods",
69-
"Access-Control-Allow-Headers",
70-
"Access-Control-Allow-Origin",
71-
},
72-
AllowCredentials: true,
73-
}))
74-
54+
enableCors(ginApp)
7555
setUpRoutes(ginApp)
7656
setUpDependencies()
7757
runBackgroundTasks(log)
58+
mountFrontend(ginApp)
7859

7960
startServerWithGracefulShutdown(log, ginApp)
8061
}
@@ -171,6 +152,10 @@ func runWithPanicLogging(log *slog.Logger, serviceName string, fn func()) {
171152
// is generated into Go files. So if we changed files, we generate
172153
// new docs, but still need to restart the server to see them.
173154
func generateSwaggerDocs(log *slog.Logger) {
155+
if config.GetEnv().EnvMode == env_utils.EnvModeProduction {
156+
return
157+
}
158+
174159
// Run swag from the current directory instead of parent
175160
// Use the current directory as the base for swag init
176161
// This ensures swag can find the files regardless of where the command is run from
@@ -212,3 +197,42 @@ func runMigrations(log *slog.Logger) {
212197

213198
log.Info("Database migrations completed successfully", "output", string(output))
214199
}
200+
201+
func enableCors(ginApp *gin.Engine) {
202+
if config.GetEnv().EnvMode == env_utils.EnvModeDevelopment {
203+
// Setup CORS
204+
ginApp.Use(cors.New(cors.Config{
205+
AllowOrigins: []string{"*"},
206+
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
207+
AllowHeaders: []string{
208+
"Origin",
209+
"Content-Length",
210+
"Content-Type",
211+
"Authorization",
212+
"Accept",
213+
"Accept-Language",
214+
"Accept-Encoding",
215+
"Access-Control-Request-Method",
216+
"Access-Control-Request-Headers",
217+
"Access-Control-Allow-Methods",
218+
"Access-Control-Allow-Headers",
219+
"Access-Control-Allow-Origin",
220+
},
221+
AllowCredentials: true,
222+
}))
223+
}
224+
}
225+
226+
func mountFrontend(ginApp *gin.Engine) {
227+
staticDir := "./ui/build"
228+
ginApp.NoRoute(func(c *gin.Context) {
229+
path := filepath.Join(staticDir, c.Request.URL.Path)
230+
231+
if info, err := os.Stat(path); err == nil && !info.IsDir() {
232+
c.File(path)
233+
return
234+
}
235+
236+
c.File(filepath.Join(staticDir, "index.html"))
237+
})
238+
}

backend/ui/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
In production during the build process, the ui is built
2+
and will be placed in this folder under /build

docker-compose.yml

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,21 @@
11
version: "3"
22

33
services:
4-
postgresus-frontend:
4+
postgresus:
55
build:
6-
context: ./frontend
7-
dockerfile: Dockerfile
8-
ports:
9-
- "4006:4006"
10-
11-
postgresus-backend:
12-
build:
13-
context: ./backend
6+
context: .
147
dockerfile: Dockerfile
158
ports:
169
- "4005:4005"
17-
volumes:
18-
- ./postgresus-data:/postgresus-data
1910

2011
postgresus-db:
21-
env_file:
22-
- .env
2312
image: postgres:17
13+
# we use default values, but do not expose
14+
# PostgreSQL ports so it is safe
2415
environment:
25-
- POSTGRES_DB=${DB_NAME}
26-
- POSTGRES_USER=${DB_USERNAME}
27-
- POSTGRES_PASSWORD=${DB_PASSWORD}
16+
- POSTGRES_DB=postgresus
17+
- POSTGRES_USER=postgresus
18+
- POSTGRES_PASSWORD=postgresus
2819
volumes:
2920
- ./pgdata:/var/lib/postgresql/data
3021
container_name: postgresus-db

0 commit comments

Comments
 (0)