Deployment

This guide covers building and running the GoApp application using Docker and Docker Compose.

Building the Docker Image

The project includes a multi-stage Dockerfile located in the docker/ directory. This approach creates a small, optimized, and secure production image.

The Dockerfile Explained

# Stage 1: Builder
FROM golang:1.23 AS builder

COPY ../ /app
WORKDIR /app
# Compile the Go application into a static binary
RUN CGO_ENABLED=0 go build -ldflags '-s -w -extldflags "-static"' -o /app/appbin *.go

# Stage 2: Final Image
FROM debian:stable-slim
LABEL MAINTAINER Author <author@example.com>

# Install CA certificates for HTTPS/TLS
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates  \
    netbase \
    && rm -rf /var/lib/apt/lists/

# Create a non-root user for security
RUN adduser --home "/appuser" --disabled-password appuser \
    --gecos "appuser,-,-,-"
USER appuser

# Copy necessary files from the builder stage
COPY --from=builder /app/cmd/server/http/web /home/appuser/app/web
COPY --from=builder /app/appbin /home/appuser/app

# Set environment variables
ENV TEMPLATES_BASEPATH=/home/appuser/app/web/templates

WORKDIR /home/appuser/app

# Expose the application port
EXPOSE 8080

# Command to run the application
CMD ["./appbin"]

Key Features:

  • Multi-Stage Build: The first stage (builder) uses the full Go SDK to compile the application. The second stage starts from a minimal debian:stable-slim base and only copies the compiled binary and necessary assets. This results in a much smaller final image.
  • Static Binary: The CGO_ENABLED=0 and -ldflags '-extldflags "-static"' flags create a completely self-contained executable, which doesn't rely on system C libraries.
  • Non-Root User: The application runs as a dedicated appuser instead of root, which is a critical security best practice to limit the potential impact of a container compromise.

Build Command

To build the image, run this command from the project's root directory:

docker build -t goapp:latest -f docker/Dockerfile .

Running with Docker

Once the image is built, you can run it as a container. You'll need to provide the database configuration as environment variables.

docker run -p 8080:8080 -p 2000:2000 --rm -ti \
  -e ENV=production \
  -e POSTGRES_HOST=<your_db_host> \
  -e POSTGRES_PORT=<your_db_port> \
  -e POSTGRES_STORENAME=<your_db_name> \
  -e POSTGRES_USERNAME=<your_db_user> \
  -e POSTGRES_PASSWORD=<your_db_pass> \
  goapp:latest

Development with Docker Compose

For local development, the docker-compose.yml file in the docker/ directory simplifies the process by managing both the application and the database services.

docker-compose.yml Overview

services:
  postgres:
    image: "postgres:17"
    environment:
      POSTGRES_PASSWORD: gauserpassword
      POSTGRES_USER: gauser
      POSTGRES_DB: goapp
    ports:
      - "5432:5432"
    networks:
      - goapp_network

  goapp:
    image: golang:1.23
    volumes:
      - ${PWD}/../:/app
    working_dir: /app
    tty: true
    environment:
      TEMPLATES_BASEPATH: /app/cmd/server/http/web/templates
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      # ... other postgres vars
    command: ["go", "run", "inits.go", "shutdown.go", "main.go"]
    ports:
      - "8080:8080"
      - "2000:2000"
    depends_on:
      - postgres
    networks:
      - goapp_network

networks:
  goapp_network:

Key Features:

  • Services: Defines two services: postgres and goapp.
  • Networking: Both services are on the same goapp_network, allowing the application to connect to the database using the hostname postgres.
  • Volumes: The application's source code is mounted as a volume. This enables live reloading; changes you make to the code on your host machine are reflected inside the container without needing to rebuild the image.
  • Environment: All necessary environment variables are set for the goapp service.

Usage

To start the development environment, navigate to the docker/ directory and run:

cd docker
docker-compose up
To stop and remove the containers, use:

docker-compose down