11# ========= BUILD FRONTEND =========
2- FROM --platform=linux/arm64 node:24-alpine AS frontend-build
2+ FROM --platform=$BUILDPLATFORM node:24-alpine AS frontend-build
3+
34WORKDIR /frontend
45
6+ # Add version for the frontend build
57ARG APP_VERSION=dev
68ENV VITE_APP_VERSION=$APP_VERSION
79
810COPY frontend/package.json frontend/package-lock.json ./
911RUN npm ci
1012COPY frontend/ ./
1113
12- RUN if [ ! -f .env ] && [ -f .env.production.example ]; then \
13- cp .env.production.example .env; \
14+ # Copy .env file (with fallback to .env.production.example)
15+ RUN if [ ! -f .env ]; then \
16+ if [ -f .env.production.example ]; then \
17+ cp .env.production.example .env; \
18+ fi; \
1419 fi
1520
1621RUN npm run build
1722
1823# ========= BUILD BACKEND =========
19- FROM --platform=linux/arm64 golang:1.23.3 AS backend-build
24+ # Backend build stage
25+ FROM --platform=$BUILDPLATFORM golang:1.23.3 AS backend-build
26+
27+ # Make TARGET args available early so tools built here match the final image arch
28+ ARG TARGETOS
29+ ARG TARGETARCH
30+
31+ # Install Go public tools needed in runtime. Use `go install` for goose so the
32+ # binary is compiled for the target architecture instead of downloading a
33+ # prebuilt binary which may have the wrong architecture (causes exec format
34+ # errors on ARM).
35+ RUN env GOBIN=/usr/local/bin GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
36+ go install github.com/pressly/goose/v3/cmd/goose@latest
37+ RUN go install github.com/swaggo/swag/cmd/
[email protected] 38+
39+ # Set working directory
2040WORKDIR /app
2141
22- # Install Go tools for ARM64
23- RUN go install github.com/pressly/goose/v3/cmd/goose@latest \
24- && go install github.com/swaggo/swag/cmd/
[email protected] 25-
42+ # Install Go dependencies
2643COPY backend/go.mod backend/go.sum ./
2744RUN go mod download
2845
29- # Copy frontend build into backend
46+ # Create required directories for embedding
3047RUN mkdir -p /app/ui/build
48+
49+ # Copy frontend build output for embedding
3150COPY --from=frontend-build /frontend/dist /app/ui/build
3251
52+ # Generate Swagger documentation
3353COPY backend/ ./
3454RUN swag init -d . -g cmd/main.go -o swagger
3555
36- RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
56+ # Compile the backend
57+ ARG TARGETOS
58+ ARG TARGETARCH
59+ ARG TARGETVARIANT
60+ RUN CGO_ENABLED=0 \
61+ GOOS=$TARGETOS \
62+ GOARCH=$TARGETARCH \
3763 go build -o /app/main ./cmd/main.go
3864
65+
3966# ========= RUNTIME =========
40- FROM --platform=linux/arm64 debian:bookworm-slim
67+ FROM --platform=$TARGETPLATFORM debian:bookworm-slim
4168
69+ # Add version metadata to runtime image
4270ARG APP_VERSION=dev
4371LABEL org.opencontainers.image.version=$APP_VERSION
4472ENV APP_VERSION=$APP_VERSION
4573
46- # Install PostgreSQL clients (13–17) and runtime deps
47- RUN apt-get update \
48- && apt-get install -y --no-install-recommends wget ca-certificates gnupg lsb-release sudo gosu \
49- && echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
50- > /etc/apt/sources.list.d/pgdg.list \
51- && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor \
52- > /etc/apt/trusted.gpg.d/postgresql.gpg \
53- && apt-get update \
54- && apt-get install -y --no-install-recommends \
55- postgresql-17 \
56- postgresql-client-13 \
57- postgresql-client-14 \
58- postgresql-client-15 \
59- postgresql-client-16 \
60- postgresql-client-17 \
61- && rm -rf /var/lib/apt/lists/*
62-
63- # Create data dir and set ownership (postgres user already exists)
64- RUN mkdir -p /postgresus-data/pgdata \
65- && chown -R postgres:postgres /postgresus-data
74+ # Install PostgreSQL server and client tools (versions 13-17)
75+ RUN apt-get update && apt-get install -y --no-install-recommends \
76+ wget ca-certificates gnupg lsb-release sudo gosu && \
77+ wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
78+ echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
79+ > /etc/apt/sources.list.d/pgdg.list && \
80+ apt-get update && \
81+ apt-get install -y --no-install-recommends \
82+ postgresql-17 postgresql-client-13 postgresql-client-14 postgresql-client-15 \
83+ postgresql-client-16 postgresql-client-17 && \
84+ rm -rf /var/lib/apt/lists/*
85+
86+ # Create postgres user and set up directories
87+ RUN useradd -m -s /bin/bash postgres || true && \
88+ mkdir -p /postgresus-data/pgdata && \
89+ chown -R postgres:postgres /postgresus-data/pgdata
6690
6791WORKDIR /app
6892
69- # Copy goose and app binary from build stage
70- COPY --from=backend-build /go/bin/goose /usr/local/bin/goose
93+ # Copy Goose from build stage
94+ COPY --from=backend-build /usr/local/bin/goose /usr/local/bin/goose
95+
96+ # Copy app binary
7197COPY --from=backend-build /app/main .
72- COPY --from=backend-build /app/ui/build ./ui/build
73- COPY --from=backend-build /app/swagger ./swagger
98+
99+ # Copy migrations directory
74100COPY backend/migrations ./migrations
75101
76- # Copy env file if present
102+ # Copy UI files
103+ COPY --from=backend-build /app/ui/build ./ui/build
104+
105+ # Copy .env file (with fallback to .env.production.example)
77106COPY backend/.env* /app/
78- RUN if [ ! -f /app/.env ] && [ -f /app/.env.production.example ]; then \
79- cp /app/.env.production.example /app/.env; \
107+ RUN if [ ! -f /app/.env ]; then \
108+ if [ -f /app/.env.production.example ]; then \
109+ cp /app/.env.production.example /app/.env; \
110+ fi; \
80111 fi
81112
82- # Startup script (no rogue \q, DSN format for goose)
83- COPY <<' EOF' /app/start.sh
113+ # Create startup script
114+ COPY <<EOF /app/start.sh
84115# !/bin/bash
85116set -e
86117
118+ # PostgreSQL 17 binary paths
87119PG_BIN="/usr/lib/postgresql/17/bin"
88120
121+ # Ensure proper ownership of data directory
89122echo "Setting up data directory permissions..."
90123mkdir -p /postgresus-data/pgdata
91124chown -R postgres:postgres /postgresus-data
92125
126+ # Initialize PostgreSQL if not already initialized
93127if [ ! -s "/postgresus-data/pgdata/PG_VERSION" ]; then
94- echo "Initializing PostgreSQL database..."
95- gosu postgres $PG_BIN/initdb -D /postgresus-data/pgdata --encoding=UTF8 --locale=C.UTF-8
96- echo "host all all 127.0.0.1/32 md5" >> /postgresus-data/pgdata/pg_hba.conf
97- echo "local all all trust" >> /postgresus-data/pgdata/pg_hba.conf
98- echo "port = 5437" >> /postgresus-data/pgdata/postgresql.conf
99- echo "listen_addresses = 'localhost'" >> /postgresus-data/pgdata/postgresql.conf
100- echo "shared_buffers = 256MB" >> /postgresus-data/pgdata/postgresql.conf
101- echo "max_connections = 100" >> /postgresus-data/pgdata/postgresql.conf
128+ echo "Initializing PostgreSQL database..."
129+ gosu postgres \$ PG_BIN/initdb -D /postgresus-data/pgdata --encoding=UTF8 --locale=C.UTF-8
130+
131+ # Configure PostgreSQL
132+ echo "host all all 127.0.0.1/32 md5" >> /postgresus-data/pgdata/pg_hba.conf
133+ echo "local all all trust" >> /postgresus-data/pgdata/pg_hba.conf
134+ echo "port = 5437" >> /postgresus-data/pgdata/postgresql.conf
135+ echo "listen_addresses = 'localhost'" >> /postgresus-data/pgdata/postgresql.conf
136+ echo "shared_buffers = 256MB" >> /postgresus-data/pgdata/postgresql.conf
137+ echo "max_connections = 100" >> /postgresus-data/pgdata/postgresql.conf
102138fi
103139
140+ # Start PostgreSQL in background
104141echo "Starting PostgreSQL..."
105- gosu postgres $PG_BIN/postgres -D /postgresus-data/pgdata -p 5437 &
106- POSTGRES_PID=$!
142+ gosu postgres \ $ PG_BIN/postgres -D /postgresus-data/pgdata -p 5437 &
143+ POSTGRES_PID=\ $ !
107144
145+ # Wait for PostgreSQL to be ready
108146echo "Waiting for PostgreSQL to be ready..."
109147for i in {1..30}; do
110- if gosu postgres $PG_BIN/pg_isready -p 5437 -h localhost >/dev/null 2>&1; then
111- echo "PostgreSQL is ready!"
112- break
113- fi
114- if [ $i -eq 30 ]; then
115- echo "PostgreSQL failed to start"
116- exit 1
117- fi
118- sleep 1
148+ if gosu postgres \ $ PG_BIN/pg_isready -p 5437 -h localhost >/dev/null 2>&1; then
149+ echo "PostgreSQL is ready!"
150+ break
151+ fi
152+ if [ \ $ i -eq 30 ]; then
153+ echo "PostgreSQL failed to start"
154+ exit 1
155+ fi
156+ sleep 1
119157done
120158
159+ # Create database and set password for postgres user
121160echo "Setting up database and user..."
122- gosu postgres $PG_BIN/psql -p 5437 -h localhost -d postgres <<'SQL'
161+ gosu postgres \ $ PG_BIN/psql -p 5437 -h localhost -d postgres << 'SQL'
123162ALTER USER postgres WITH PASSWORD 'Q1234567' ;
163+ CREATE DATABASE "postgresus" OWNER postgres;
164+ \q
124165SQL
125166
126- # Create the database using createdb (avoids running CREATE DATABASE inside a function)
127- if ! gosu postgres $PG_BIN/psql -p 5437 -h localhost -tAc "SELECT 1 FROM pg_database WHERE datname='postgresus'" | grep -q 1; then
128- echo "Creating database 'postgresus'..."
129- gosu postgres $PG_BIN/createdb -p 5437 -h localhost -O postgres postgresus || {
130- echo "Failed to create database 'postgresus'"
131- exit 1
132- }
133- else
134- echo "Database 'postgresus' already exists"
135- fi
136-
137- echo "Running database migrations..."
138- # Use goose with explicit driver and DB string; also export env vars so goose can pick them up if it expects
139- export GOOSE_DRIVER=postgres
140- export GOOSE_DBSTRING="postgres://postgres:Q1234567@localhost:5437/postgresus?sslmode=disable"
141- # Run goose with env vars set (goose recognizes GOOSE_DRIVER and GOOSE_DBSTRING)
142- /usr/local/bin/goose -dir ./migrations up
143-
167+ # Start the main application
144168echo "Starting Postgresus application..."
145169exec ./main
146170EOF
147171
148172RUN chmod +x /app/start.sh
149173
150174EXPOSE 4005
175+
176+ # Volume for PostgreSQL data
151177VOLUME ["/postgresus-data" ]
152178
153- CMD ["/app/start.sh" ]
179+ ENTRYPOINT ["/app/start.sh" ]
180+ CMD []
0 commit comments