Skip to content

Latest commit

 

History

History
789 lines (588 loc) · 17.4 KB

File metadata and controls

789 lines (588 loc) · 17.4 KB

ActaLog Docker Deployment Guide

This guide covers building, publishing, and deploying ActaLog using Docker containers with GitHub Container Registry (ghcr.io).

Table of Contents


Overview

ActaLog uses a multi-stage Docker build that creates an optimized production image containing both the Go backend and Vue.js frontend. Images are published to GitHub Container Registry for easy distribution and deployment.

Image Location: ghcr.io/OWNER/REPO:TAG

Key Features:

  • Multi-stage build (frontend, backend, runtime)
  • Optimized Alpine-based runtime (< 50MB)
  • Multi-architecture support (amd64, arm64)
  • Automated builds on every push
  • Build number tagging for rapid testing
  • Semantic versioning for stable releases

Prerequisites

Local Development

  • Docker 20.10+ with BuildKit
  • Docker Compose 2.0+
  • Git
  • GitHub account with repository access

GitHub Container Registry

  • GitHub Personal Access Token (PAT) with write:packages permission
  • Repository must be public OR you need read:packages on private repos

Quick Start

1. Pull and Run Latest Image

# Pull latest image from GitHub Container Registry
docker pull ghcr.io/OWNER/REPO:latest

# Run with default SQLite database
docker run -d \
  --name actalog \
  -p 8080:8080 \
  -v actalog-data:/app/data \
  -v actalog-uploads:/app/uploads \
  -e JWT_SECRET=your_secure_secret_here \
  ghcr.io/OWNER/REPO:latest

# Access the application
open http://localhost:8080

2. Using Docker Compose (Recommended)

cd docker

# Copy and configure environment
cp .env.example .env
# Edit .env with your configuration

# Start the application
docker compose up -d

# View logs
docker compose logs -f

# Stop the application
docker compose down

Building Images

Using Build Script (Recommended)

The build script automatically extracts version information and creates properly tagged images.

# Build with default 'dev' tag
./docker/scripts/build.sh

# Build with custom tag
./docker/scripts/build.sh v0.9.0

# Build for specific platform
DOCKER_PLATFORM=linux/arm64 ./docker/scripts/build.sh

# Build for multiple platforms (requires buildx)
DOCKER_PLATFORM=linux/amd64,linux/arm64 ./docker/scripts/build.sh

What the script does:

  1. Extracts version and build number from pkg/version/version.go
  2. Builds multi-stage Docker image
  3. Tags image with:
    • Custom tag (or 'dev')
    • Build number tag (build-62)
  4. Loads image into local Docker daemon
  5. Displays pull and push instructions

Manual Build

# From project root
docker build \
  -f docker/Dockerfile \
  -t ghcr.io/OWNER/REPO:dev \
  .

# Multi-platform build
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -f docker/Dockerfile \
  -t ghcr.io/OWNER/REPO:dev \
  --push \
  .

Publishing to GitHub Container Registry

1. Authenticate with GitHub

# Option 1: Using GitHub CLI (recommended)
gh auth login

# Option 2: Using Personal Access Token
export GITHUB_TOKEN=ghp_yourpersonalaccesstoken
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin

# Option 3: Interactive login
docker login ghcr.io
# Username: YOUR_GITHUB_USERNAME
# Password: YOUR_PERSONAL_ACCESS_TOKEN

2. Push Image Using Script

# Push image with specific tag
./docker/scripts/push.sh dev

# Push stable release
./docker/scripts/push.sh v0.9.0

What the script does:

  1. Checks GitHub Container Registry authentication
  2. Pushes image with specified tag
  3. Pushes build-specific tag (build-62)
  4. Displays pull command and package URL

3. Manual Push

# Push specific tag
docker push ghcr.io/OWNER/REPO:dev

# Push multiple tags
docker push ghcr.io/OWNER/REPO:v0.9.0
docker push ghcr.io/OWNER/REPO:latest

4. Make Package Public (Optional)

After first push, packages are private by default.

  1. Go to https://github.com/OWNER/REPO/packages
  2. Click on actalog package
  3. Go to Package settings
  4. Scroll to Danger Zone
  5. Click Change visibilityPublic

Automated Builds with GitHub Actions

How It Works

The GitHub Actions workflow (.github/workflows/docker-build.yml) automatically builds and pushes Docker images on:

  • Push to main or develop branches � builds and pushes
  • Push tags matching v* � builds release with semantic versioning
  • Pull requests to main � builds only (no push)
  • Manual workflow dispatch � build with custom tag

Automatic Tagging Strategy

Images are automatically tagged based on the event:

Event Tags Created Example
Push to main latest, main-SHA, build-N latest, main-a1b2c3d, build-42
Push to develop develop, develop-SHA, build-N develop, develop-x9y8z7w, build-43
Tag v1.2.3 v1.2.3, v1.2, v1, build-N v1.2.3, v1.2, v1, build-44
PR #123 pr-123 pr-123
Manual trigger Custom tag, build-N manual, build-45

Required Secrets

No secrets required! The workflow uses the built-in GITHUB_TOKEN which has automatic access to GitHub Container Registry.

Viewing Build Status

  1. Go to Actions tab in GitHub
  2. Click on Build and Push Docker Image workflow
  3. View build summary with:
    • Build metadata
    • All tags created
    • Pull command

Rapid Testing with Development Builds

Problem: Testing Remote Deployments Quickly

You need to test changes on a remote server without creating formal releases.

Solution: Build Number Tagging

Every build automatically gets a unique build-N tag based on GitHub Actions run number.

Workflow:

# 1. Push code to main/develop branch
git add .
git commit -m "feat: add new feature"
git push origin main

# 2. GitHub Actions automatically builds and tags:
#    - ghcr.io/OWNER/REPO:main-abc1234
#    - ghcr.io/OWNER/REPO:build-62
#    - ghcr.io/OWNER/REPO:latest

# 3. On remote server, pull latest build
ssh user@remote-server
docker pull ghcr.io/OWNER/REPO:build-62

# 4. Update docker-compose.yml or restart container
export TAG=build-62
docker compose up -d

# 5. Verify deployment
docker ps
curl http://localhost:8080/api/version

Fast Iteration Cycle

# Local development
make build                    # Increments build number
git add . && git commit -m "..." && git push

# Watch GitHub Actions (2-5 minutes)
gh run watch

# Deploy on remote
ssh user@server "docker pull ghcr.io/OWNER/REPO:build-63 && docker compose up -d"

# Test
curl http://server:8080/api/version
# {"version":"0.9.0-beta","build":63,...}

Automated Remote Deployment Script

Create scripts/deploy-dev.sh:

#!/bin/bash
set -e

SERVER="user@your-server.com"
BUILD_NUM=$(grep -E "^\s*Build\s*=\s*[0-9]+" pkg/version/version.go | awk '{print $3}')

echo "Deploying build ${BUILD_NUM} to ${SERVER}..."

# SSH and pull latest build
ssh $SERVER << EOF
  cd /opt/actalog
  export TAG=build-${BUILD_NUM}
  docker pull ghcr.io/OWNER/REPO:\$TAG
  docker compose up -d
  docker ps
  curl -s http://localhost:8080/api/version | jq .
EOF

echo "Deployment complete!"

Usage:

# Make changes
vim internal/handler/some_handler.go

# Build, commit, push
make build
git add . && git commit -m "fix: bug fix" && git push

# Wait for GitHub Actions, then deploy
gh run watch && ./scripts/deploy-dev.sh

Production Deployment

Using Docker Compose (Recommended)

1. Create deployment directory:

ssh user@production-server
mkdir -p /opt/actalog
cd /opt/actalog

2. Copy docker-compose.yml and .env:

# Download from repository
curl -O https://raw.githubusercontent.com/OWNER/REPO/main/docker/docker-compose.yml
curl -O https://raw.githubusercontent.com/OWNER/REPO/main/docker/.env.example
mv .env.example .env

3. Configure environment:

vim .env

Required configuration:

GITHUB_OWNER=your-github-username
TAG=v0.9.0                    # Use stable release tag

# Change this!
JWT_SECRET=GENERATE_SECURE_RANDOM_STRING_HERE

# Optional: PostgreSQL instead of SQLite
DB_DRIVER=postgres
DB_HOST=postgres
DB_PORT=5432
DB_NAME=actalog
DB_USER=actalog
DB_PASSWORD=secure_password

4. Start services:

docker compose up -d

5. Verify deployment:

docker compose ps
docker compose logs -f
curl http://localhost:8080/health
curl http://localhost:8080/api/version

Using Standalone Docker

docker run -d \
  --name actalog \
  --restart unless-stopped \
  -p 8080:8080 \
  -v actalog-data:/app/data \
  -v actalog-uploads:/app/uploads \
  -e DB_DRIVER=sqlite3 \
  -e DB_NAME=/app/data/actalog.db \
  -e JWT_SECRET=your_secure_secret_here \
  -e CORS_ORIGINS=https://yourdomain.com \
  ghcr.io/OWNER/REPO:v0.9.0

Production with PostgreSQL

docker-compose.yml:

version: '3.8'

services:
  actalog:
    image: ghcr.io/OWNER/REPO:v0.9.0
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - DB_DRIVER=postgres
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=actalog
      - DB_USER=actalog
      - DB_PASSWORD=${DB_PASSWORD}
      - JWT_SECRET=${JWT_SECRET}
    volumes:
      - actalog-uploads:/app/uploads
    depends_on:
      - postgres
    networks:
      - actalog-network

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=actalog
      - POSTGRES_USER=actalog
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - actalog-network

volumes:
  actalog-uploads:
  postgres-data:

networks:
  actalog-network:

Creating Stable Releases

Release Process

Stable releases use semantic versioning tags (v1.2.3) and are automatically built by GitHub Actions.

1. Update version in code:

Edit pkg/version/version.go:

const (
    Major = 0
    Minor = 9
    Patch = 0
    PreRelease = "beta"  // or "" for stable
    Build = 62  // Auto-incremented
)

Edit web/package.json:

{
  "version": "0.9.0"
}

2. Update changelog:

Edit docs/CHANGELOG.md:

## [0.9.0-beta] - 2025-01-23

### Added
- Feature X
- Feature Y

### Fixed
- Bug Z

3. Commit and push changes:

git add pkg/version/version.go web/package.json docs/CHANGELOG.md
git commit -m "chore: bump version to v0.9.0-beta"
git push origin main

4. Create and push Git tag:

# Create annotated tag
git tag -a v0.9.0-beta -m "Release v0.9.0-beta

- Offline PWA support
- Docker deployment
- 1RM calculation
- Performance improvements"

# Push tag to GitHub
git push origin v0.9.0-beta

5. GitHub Actions automatically:

  • Detects the tag
  • Builds multi-platform images
  • Tags images with:
    • v0.9.0-beta
    • v0.9
    • v0
    • build-62
  • Pushes to GitHub Container Registry
  • Creates build summary

6. Create GitHub Release (optional):

# Using GitHub CLI
gh release create v0.9.0-beta \
  --title "ActaLog v0.9.0-beta" \
  --notes-file docs/CHANGELOG.md \
  --prerelease

# Or manually at: https://github.com/OWNER/REPO/releases/new

7. Verify release:

# Pull release image
docker pull ghcr.io/OWNER/REPO:v0.9.0-beta

# Verify version
docker run --rm ghcr.io/OWNER/REPO:v0.9.0-beta /app/actalog --version
# ActaLog v0.9.0-beta+build.62

Release Channels

Channel Tag Pattern Use Case Stability
latest Auto from main Latest stable Production-ready
develop Auto from develop Bleeding edge Testing
v1.2.3 Git tag Specific version Production
v1.2 Auto from tag Minor version Production
v1 Auto from tag Major version Production
build-N Every build Rapid testing Development

For End Users

Stable Release (Recommended):

docker pull ghcr.io/OWNER/REPO:v0.9.0

Latest Stable:

docker pull ghcr.io/OWNER/REPO:latest

Specific Minor Version (auto-updates patches):

docker pull ghcr.io/OWNER/REPO:v0.9

Beta/Pre-release:

docker pull ghcr.io/OWNER/REPO:v0.9.0-beta

Troubleshooting

Build Failures

Problem: Docker build fails with "npm ci" errors

Solution:

# Clear npm cache in Dockerfile or rebuild without cache
docker build --no-cache -f docker/Dockerfile .

Problem: Go build fails with "missing module"

Solution:

# Ensure go.mod and go.sum are up to date
go mod tidy
git add go.mod go.sum
git commit -m "chore: update go modules"

Push Failures

Problem: "unauthorized: authentication required"

Solution:

# Re-authenticate
docker logout ghcr.io
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_USERNAME --password-stdin

Problem: "denied: permission_denied"

Solution:

  • Verify your GitHub token has write:packages permission
  • Ensure you have push access to the repository
  • Check package visibility settings

Runtime Issues

Problem: Container starts but /health returns 404

Solution:

# Check logs
docker logs actalog

# Verify environment variables
docker exec actalog env | grep DB_

# Check if migrations ran
docker exec actalog ls -la /app/migrations

Problem: Database connection errors

Solution:

# For SQLite
docker exec actalog ls -la /app/data/

# For PostgreSQL
docker exec actalog ping postgres
docker compose logs postgres

Connecting to Host Database (MariaDB/MySQL/PostgreSQL)

Problem: Container can't connect to database running on the host machine

dial tcp 127.0.0.1:3306: connect: connection refused

This happens because 127.0.0.1 inside a container refers to the container itself, not the host machine.

Solution for Linux:

  1. Update docker-compose.yml to use host.docker.internal:
services:
  actalog:
    image: ghcr.io/johnzastrow/actalog:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Required for Linux
    environment:
      - DB_DRIVER=mysql
      - DB_HOST=host.docker.internal  # NOT 127.0.0.1
      - DB_PORT=3306
      - DB_NAME=acta
      - DB_USER=acta
      - DB_PASSWORD=${DB_PASSWORD}

Alternative: Use Docker bridge IP (172.17.0.1) instead of host.docker.internal.

  1. Configure MariaDB/MySQL to accept connections from Docker:

Check that MariaDB is listening on all interfaces (not just localhost):

sudo grep -E "bind-address|skip-networking" /etc/mysql/mariadb.conf.d/*.cnf

If bind-address = 127.0.0.1, change to bind-address = 0.0.0.0 or comment it out, then restart MariaDB:

sudo systemctl restart mariadb
  1. Grant database user permission to connect from Docker network:
-- For Docker bridge network (most secure)
GRANT ALL PRIVILEGES ON acta.* TO 'acta'@'172.17.%' IDENTIFIED BY 'your_password';
FLUSH PRIVILEGES;

-- Or allow from any host (less secure, but easier for testing)
GRANT ALL PRIVILEGES ON acta.* TO 'acta'@'%' IDENTIFIED BY 'your_password';
FLUSH PRIVILEGES;
  1. Verify connectivity:
# Restart container
docker compose down && docker compose up -d

# Check logs
docker compose logs -f actalog

# Test from inside container
docker exec -it actalog sh -c "nc -zv host.docker.internal 3306"

Note for Docker Desktop (Mac/Windows): The extra_hosts directive is not needed—host.docker.internal works automatically.

  1. Check firewall rules (if connection times out):

If the connection times out instead of being refused, a firewall may be blocking Docker's network:

# Check UFW status
sudo ufw status

# Allow MariaDB from Docker network
sudo ufw allow from 172.17.0.0/16 to any port 3306
sudo ufw reload

# For PostgreSQL
sudo ufw allow from 172.17.0.0/16 to any port 5432
sudo ufw reload

Image Pull Issues

Problem: "pull access denied"

Solution:

# For private repositories, authenticate first
docker login ghcr.io

# Or make package public (see Publishing section)

Problem: "manifest unknown"

Solution:

# Verify tag exists
gh api /user/packages/container/actalog/versions

# Or check at: https://github.com/OWNER/REPO/pkgs/container/actalog

Additional Resources


Summary Commands Cheat Sheet

# Build locally
./docker/scripts/build.sh v0.9.0

# Push to registry
./docker/scripts/push.sh v0.9.0

# Pull specific build for testing
docker pull ghcr.io/OWNER/REPO:build-62

# Deploy with Docker Compose
cd docker && docker compose up -d

# Create stable release
git tag -a v0.9.0 -m "Release v0.9.0"
git push origin v0.9.0

# Pull stable release
docker pull ghcr.io/OWNER/REPO:v0.9.0