# 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](#overview) - [Prerequisites](#prerequisites) - [Quick Start](#quick-start) - [Building Images](#building-images) - [Publishing to GitHub Container Registry](#publishing-to-github-container-registry) - [Automated Builds with GitHub Actions](#automated-builds-with-github-actions) - [Rapid Testing with Development Builds](#rapid-testing-with-development-builds) - [Production Deployment](#production-deployment) - [Creating Stable Releases](#creating-stable-releases) - [Troubleshooting](#troubleshooting) --- ## 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 ```bash # 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) ```bash 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. ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 visibility** � **Public** --- ## 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:** ```bash # 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 ```bash # 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`: ```bash #!/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:** ```bash # 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:** ```bash ssh user@production-server mkdir -p /opt/actalog cd /opt/actalog ``` **2. Copy docker-compose.yml and .env:** ```bash # 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:** ```bash vim .env ``` **Required configuration:** ```env 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:** ```bash docker compose up -d ``` **5. Verify deployment:** ```bash docker compose ps docker compose logs -f curl http://localhost:8080/health curl http://localhost:8080/api/version ``` ### Using Standalone Docker ```bash 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:** ```yaml 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`: ```go const ( Major = 0 Minor = 9 Patch = 0 PreRelease = "beta" // or "" for stable Build = 62 // Auto-incremented ) ``` Edit `web/package.json`: ```json { "version": "0.9.0" } ``` **2. Update changelog:** Edit `docs/CHANGELOG.md`: ```markdown ## [0.9.0-beta] - 2025-01-23 ### Added - Feature X - Feature Y ### Fixed - Bug Z ``` **3. Commit and push changes:** ```bash 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:** ```bash # 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):** ```bash # 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:** ```bash # 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):** ```bash docker pull ghcr.io/OWNER/REPO:v0.9.0 ``` **Latest Stable:** ```bash docker pull ghcr.io/OWNER/REPO:latest ``` **Specific Minor Version (auto-updates patches):** ```bash docker pull ghcr.io/OWNER/REPO:v0.9 ``` **Beta/Pre-release:** ```bash docker pull ghcr.io/OWNER/REPO:v0.9.0-beta ``` --- ## Troubleshooting ### Build Failures **Problem:** Docker build fails with "npm ci" errors **Solution:** ```bash # Clear npm cache in Dockerfile or rebuild without cache docker build --no-cache -f docker/Dockerfile . ``` **Problem:** Go build fails with "missing module" **Solution:** ```bash # 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:** ```bash # 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:** ```bash # 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:** ```bash # 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`: ```yaml 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`. 2. **Configure MariaDB/MySQL to accept connections from Docker:** Check that MariaDB is listening on all interfaces (not just localhost): ```bash 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: ```bash sudo systemctl restart mariadb ``` 3. **Grant database user permission to connect from Docker network:** ```sql -- 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; ``` 4. **Verify connectivity:** ```bash # 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. 5. **Check firewall rules (if connection times out):** If the connection times out instead of being refused, a firewall may be blocking Docker's network: ```bash # 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:** ```bash # For private repositories, authenticate first docker login ghcr.io # Or make package public (see Publishing section) ``` **Problem:** "manifest unknown" **Solution:** ```bash # Verify tag exists gh api /user/packages/container/actalog/versions # Or check at: https://github.com/OWNER/REPO/pkgs/container/actalog ``` --- ## Additional Resources - [Docker Documentation](https://docs.docker.com/) - [GitHub Container Registry Docs](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) - [GitHub Actions Docker Build](https://docs.docker.com/build/ci/github-actions/) - [Docker Multi-platform Builds](https://docs.docker.com/build/building/multi-platform/) --- ## Summary Commands Cheat Sheet ```bash # 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 ```