Add Docker setup: one script to set up/start/stop the full stack

A new developer needs only Docker Desktop — no local Go, Node, or PostgreSQL.

- docker-compose.yml: PostgreSQL + Go API + Angular (nginx) with healthcheck,
  migrations auto-applied via docker-entrypoint-initdb.d, data in a volume
- backend/Dockerfile: static Go binary on distroless (nonroot)
- frontend/Dockerfile: Angular prod build served by nginx, which proxies /api
  to the api service (same origin, no CORS)
- docker.ps1: setup / start / stop / restart / reset / rebuild / status / logs;
  reads DB creds from backend/.env (single source, nothing duplicated)
- .dockerignore keeps .env and local artifacts out of image build contexts
- README: "Run with Docker" quick start

Not yet run end to end here (Docker is not installed on this machine); the
compose and script are validated for syntax and structure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nilesh.p
2026-10-10 13:39:15 +05:30
co-authored by Claude Opus 4.8
parent 17a7e8ffd5
commit a9dc273e09
8 changed files with 363 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
#requires -Version 5.1
<#
.SYNOPSIS
Set up, start and stop the entire TravelHub stack on Docker. A new developer
needs only Docker Desktop -- no local Go, Node, or PostgreSQL.
.DESCRIPTION
Wraps docker compose for the three services (PostgreSQL + Go API + Angular
via nginx). Database credentials are read from backend/.env, so there is one
source of truth and no secret is duplicated.
URLs once up:
App -> http://localhost:4200 (nginx serves the SPA and proxies /api)
API -> http://localhost:8080
Demo sign-in: any seeded account, password demo1234
(e.g. agent@skyline.test, agency "skyline").
.PARAMETER Action
setup Build images and bring everything up, waiting until healthy. The
database is created and seeded on this first run. One command to
onboard.
start Bring the stack up (images already built).
stop Stop and remove the containers; the database volume is kept.
restart stop then start.
reset Stop and DELETE the database volume, then start fresh (re-seeds).
rebuild Rebuild images (use after pulling code changes) and start.
status Show container state.
logs Tail logs (-Follow to stream; -Service to pick one).
.PARAMETER Service
logs: which service (postgres | api | web). Default: all.
.PARAMETER Follow
logs: stream instead of printing the tail once.
.EXAMPLE
.\docker.ps1 setup
.EXAMPLE
.\docker.ps1 logs -Service api -Follow
.EXAMPLE
.\docker.ps1 reset # wipe data and re-seed with fresh flight dates
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[ValidateSet('setup', 'start', 'stop', 'restart', 'reset', 'rebuild', 'status', 'logs', 'help')]
[string]$Action = 'help',
[ValidateSet('postgres', 'api', 'web')]
[string]$Service,
[switch]$Follow
)
$ErrorActionPreference = 'Stop'
$Root = $PSScriptRoot
function Write-Step($m) { Write-Host "==> $m" -ForegroundColor Cyan }
function Write-Ok($m) { Write-Host " + $m" -ForegroundColor Green }
function Write-Warn2($m){ Write-Host " ! $m" -ForegroundColor Yellow }
# --- docker availability ---------------------------------------------------
function Require-Docker {
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
throw "Docker not found. Install Docker Desktop and ensure it is running: https://www.docker.com/products/docker-desktop/"
}
# `docker info` fails fast if the engine/daemon is not running.
docker info *> $null
if ($LASTEXITCODE -ne 0) {
throw 'Docker is installed but the engine is not running. Start Docker Desktop and retry.'
}
# Compose v2 (the `docker compose` subcommand) is required.
docker compose version *> $null
if ($LASTEXITCODE -ne 0) {
throw "This script needs Docker Compose v2 (the 'docker compose' subcommand). Update Docker Desktop."
}
}
# --- credentials from backend/.env -----------------------------------------
# Load DB_USER / DB_PASSWORD / DB_NAME from backend/.env into this process so
# docker compose can substitute them. Nothing is printed or copied to disk.
function Import-DbEnv {
$envFile = Join-Path $Root 'backend\.env'
if (-not (Test-Path $envFile)) {
throw "backend\.env not found. Copy backend\.env.example to backend\.env and set DB_PASSWORD first."
}
foreach ($line in Get-Content $envFile) {
$t = $line.Trim()
if ($t -eq '' -or $t.StartsWith('#')) { continue }
$i = $t.IndexOf('=')
if ($i -lt 1) { continue }
$key = $t.Substring(0, $i).Trim()
$val = $t.Substring($i + 1).Trim().Trim('"').Trim("'")
if ($key -in @('DB_USER', 'DB_PASSWORD', 'DB_NAME')) {
Set-Item -Path "Env:$key" -Value $val
}
}
if (-not $env:DB_PASSWORD) {
throw 'DB_PASSWORD is not set in backend\.env.'
}
if (-not $env:DB_USER) { $env:DB_USER = 'postgres' }
if (-not $env:DB_NAME) { $env:DB_NAME = 'travelhub' }
}
# --- compose helpers -------------------------------------------------------
function Compose {
param([Parameter(ValueFromRemainingArguments = $true)] $ComposeArgs)
Push-Location $Root
try { & docker compose @ComposeArgs } finally { Pop-Location }
}
function Wait-ForUrl($url, $timeoutSec) {
$deadline = (Get-Date).AddSeconds($timeoutSec)
while ((Get-Date) -lt $deadline) {
try { Invoke-WebRequest $url -UseBasicParsing -TimeoutSec 3 | Out-Null; return $true }
catch { Start-Sleep -Seconds 2 }
}
return $false
}
function Show-Ready {
Write-Host ''
if (Wait-ForUrl 'http://localhost:8080/api/health' 90) { Write-Ok 'API healthy -> http://localhost:8080' }
else { Write-Warn2 'API not healthy yet -- check: .\docker.ps1 logs -Service api' }
if (Wait-ForUrl 'http://localhost:4200/' 60) { Write-Ok 'App ready -> http://localhost:4200' }
else { Write-Warn2 'Web not ready yet -- check: .\docker.ps1 logs -Service web' }
Write-Host ''
Write-Host 'Sign in with any demo account, password demo1234 (e.g. agent@skyline.test / skyline).'
Write-Host 'Note: stop any native .\app.ps1 first -- it uses the same ports.' -ForegroundColor DarkGray
}
# --- actions ---------------------------------------------------------------
switch ($Action) {
'setup' {
Require-Docker; Import-DbEnv
Write-Step 'Building images (first run downloads base images)'
Compose build
Write-Step 'Starting stack and seeding the database'
Compose up -d
Show-Ready
Write-Ok 'Setup complete.'
}
'start' {
Require-Docker; Import-DbEnv
Write-Step 'Starting stack'
Compose up -d
Show-Ready
}
'stop' {
Require-Docker; Import-DbEnv
Write-Step 'Stopping stack (database volume kept)'
Compose down
Write-Ok 'Stopped.'
}
'restart' {
Require-Docker; Import-DbEnv
Compose down
Compose up -d
Show-Ready
}
'reset' {
Require-Docker; Import-DbEnv
Write-Warn2 'This DELETES the database volume. All data will be lost.'
Write-Step 'Removing containers and the data volume'
Compose down -v
Write-Step 'Starting fresh (re-seeds with current flight dates)'
Compose up -d
Show-Ready
Write-Ok 'Reset complete.'
}
'rebuild' {
Require-Docker; Import-DbEnv
Write-Step 'Rebuilding images'
Compose build --no-cache
Compose up -d
Show-Ready
}
'status' {
Require-Docker; Import-DbEnv
Compose ps
}
'logs' {
Require-Docker; Import-DbEnv
$logArgs = @('logs')
if ($Follow) { $logArgs += '-f' } else { $logArgs += @('--tail', '80') }
if ($Service) { $logArgs += $Service }
Compose @logArgs
}
default {
Get-Help $PSCommandPath -Detailed
}
}