Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions .github/workflows/synkronus-docker.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Synkronus Docker Build & Publish
name: Synkronus & Portal Docker Build & Publish

on:
push:
Expand All @@ -7,10 +7,16 @@ on:
- dev
paths:
- 'synkronus/**'
- 'synkronus-portal/**'
- 'packages/**'
- 'Dockerfile'
- '.github/workflows/synkronus-docker.yml'
pull_request:
paths:
- 'synkronus/**'
- 'synkronus-portal/**'
- 'packages/**'
- 'Dockerfile'
- '.github/workflows/synkronus-docker.yml'
workflow_dispatch:
inputs:
Expand All @@ -23,11 +29,11 @@ on:

env:
REGISTRY: ghcr.io
IMAGE_NAME: opendataensemble/synkronus

jobs:
build-and-push:
runs-on: ubuntu-latest
name: Build and push combined Synkronus image
permissions:
contents: write
packages: write
Expand Down Expand Up @@ -57,7 +63,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/opendataensemble/synkronus
tags: |
# For main branch: latest + version tag (manual dispatch) or release tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
Expand All @@ -78,15 +84,15 @@ jobs:
type=sha,prefix=sha-,enable=${{ github.event_name != 'release' && github.event_name != 'pull_request' }}
labels: |
org.opencontainers.image.title=Synkronus
org.opencontainers.image.description=Synchronization API for offline-first applications
org.opencontainers.image.description=Synchronization API and Web Portal for offline-first applications
org.opencontainers.image.vendor=Open Data Ensemble

- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: ./synkronus
file: ./synkronus/Dockerfile
context: .
file: ./Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
Expand All @@ -98,6 +104,6 @@ jobs:
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-name: ${{ env.REGISTRY }}/opendataensemble/synkronus
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
100 changes: 0 additions & 100 deletions .github/workflows/synkronus-portal-docker.yml

This file was deleted.

40 changes: 40 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Docker Build and Run Guide

This guide explains how to build and run the combined Synkronus Docker image, which includes both the Synkronus API backend and the Portal frontend in a single container.

## Prerequisites

- Docker and Docker Compose installed
- PostgreSQL database (run as separate container or use existing database)

## Quick Start with Docker Compose (Recommended)

1. **Update credentials** in `docker-compose.yml`:
- `POSTGRES_PASSWORD`: Change `your_password`
- `DB_CONNECTION`: Update password in connection string
- `JWT_SECRET`: Generate with `openssl rand -base64 32`

2. **Start all services**:
```bash
docker compose up -d
```


## Docker Compose Commands

```bash
# Start services
docker compose up -d

# View logs
docker compose logs -f

# Check status
docker compose ps

# Stop services
docker compose down

# Stop and remove volumes (WARNING: deletes data)
docker compose down -v
```
163 changes: 163 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Multi-stage build for combined Synkronus + Portal
# Stage 1: Build the Go application (Synkronus)
FROM golang:1.24.2-alpine AS synkronus-builder

# Install build dependencies
RUN apk add --no-cache git

# Set working directory
WORKDIR /app

# Copy go mod files
COPY synkronus/go.mod synkronus/go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY synkronus/ .

# Build the application
ENV CGO_ENABLED=0 GOOS=linux
RUN go build -a -ldflags='-w -s' -o synkronus ./cmd/synkronus

# Stage 2: Build the React application (Portal)
FROM node:20-alpine AS portal-builder

WORKDIR /app

# Copy package files for all packages (monorepo structure)
COPY packages/tokens/package*.json ./packages/tokens/
COPY packages/tokens/package-lock.json ./packages/tokens/
COPY packages/tokens/style-dictionary.config.js ./packages/tokens/
COPY packages/tokens/config.json ./packages/tokens/
COPY packages/tokens/src ./packages/tokens/src
COPY packages/components/package*.json ./packages/components/
COPY synkronus-portal/package*.json ./synkronus-portal/
COPY synkronus-portal/package-lock.json ./synkronus-portal/

# Install dependencies for tokens
WORKDIR /app/packages/tokens
RUN npm ci

# Install dependencies for components
WORKDIR /app/packages/components
RUN npm install

# Install dependencies for portal
WORKDIR /app/synkronus-portal
RUN npm ci

# Copy source code for all packages
WORKDIR /app
COPY packages/tokens ./packages/tokens
COPY packages/components ./packages/components
COPY synkronus-portal ./synkronus-portal

# Build tokens first (if needed)
WORKDIR /app/packages/tokens
RUN npm run build || true

# Build components (if needed)
WORKDIR /app/packages/components
RUN npm run build || true

# Build the portal application
WORKDIR /app/synkronus-portal
RUN npm run build

# Stage 3: Combine both in final image
FROM nginx:alpine

# Install runtime dependencies for synkronus (wget for healthcheck, su-exec for user switching)
RUN apk --no-cache add ca-certificates tzdata wget su-exec

# Create non-root user for synkronus
RUN addgroup -g 1000 synkronus && \
adduser -D -u 1000 -G synkronus synkronus

# Copy synkronus binary and assets from builder
WORKDIR /app
COPY --from=synkronus-builder /app/synkronus /app/synkronus
COPY --from=synkronus-builder /app/openapi /app/openapi
COPY --from=synkronus-builder /app/static /app/static

# Create directories for data storage with proper permissions
RUN mkdir -p /app/data/app-bundles && \
chown -R synkronus:synkronus /app

# Copy portal built assets
COPY --from=portal-builder /app/synkronus-portal/dist /usr/share/nginx/html

# Create nginx configuration
# Proxy /api requests to local synkronus backend on port 8080
RUN echo 'server { \
listen 0.0.0.0:80; \
listen [::]:80; \
server_name _; \
root /usr/share/nginx/html; \
index index.html; \
\
# Serve portal frontend \
location / { \
try_files $uri $uri/ /index.html; \
} \
\
# Proxy API requests to local synkronus backend \
location /api { \
rewrite ^/api(.*)$ $1 break; \
proxy_pass http://127.0.0.1:8080; \
proxy_http_version 1.1; \
proxy_set_header Upgrade $http_upgrade; \
proxy_set_header Connection "upgrade"; \
proxy_set_header Host $host; \
proxy_set_header X-Real-IP $remote_addr; \
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; \
proxy_set_header X-Forwarded-Proto $scheme; \
proxy_set_header X-Forwarded-Host $host; \
proxy_set_header Authorization $http_authorization; \
proxy_pass_request_headers on; \
proxy_connect_timeout 60s; \
proxy_send_timeout 60s; \
proxy_read_timeout 60s; \
proxy_buffering off; \
client_max_body_size 100M; \
} \
}' > /etc/nginx/conf.d/default.conf

# Create startup script to run both services
RUN printf '#!/bin/sh\n\
set -e\n\
\n\
# Function to handle shutdown\n\
cleanup() {\n\
echo "Shutting down..."\n\
kill -TERM "$synkronus_pid" 2>/dev/null || true\n\
nginx -s quit 2>/dev/null || true\n\
wait "$synkronus_pid" 2>/dev/null || true\n\
exit 0\n\
}\n\
\n\
# Trap signals for graceful shutdown\n\
trap cleanup SIGTERM SIGINT\n\
\n\
# Start synkronus in background as non-root user\n\
su-exec synkronus /app/synkronus &\n\
synkronus_pid=$!\n\
\n\
# Wait a moment for synkronus to start\n\
sleep 2\n\
\n\
# Start nginx in foreground (this blocks, shell remains PID 1 for signal handling)\n\
nginx -g "daemon off;"\n\
' > /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh

EXPOSE 80

# Health check - check synkronus health endpoint through nginx proxy
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 -O - http://127.0.0.1/api/health || exit 1

# Use custom entrypoint to run both services
ENTRYPOINT ["/docker-entrypoint.sh"]

Loading
Loading