notes / Deployment
Docker Deployment Scripts for Small Services
Field notes from creating small deployment helper scripts for Docker-based services so build, run, restart, logs, cleanup, and updates are repeatable.
Why This Note Exists
Running a service with Docker is simple the first time.
Maintaining it becomes annoying when every update requires remembering the exact build command, container name, environment file, network mode, restart policy, logs command, and cleanup steps.
This note documents the practical direction for creating small helper scripts around Docker services.
The goal is not to hide Docker completely. The goal is to make common operations repeatable so deployment becomes less error-prone.
Context
This kind of scripting is useful for small services such as:
- bots
- lightweight APIs
- internal tools
- homelab services
- small automation workers
- personal utilities
- services deployed on a VPS or OpenWrt-capable device
The service may be simple, but the operational steps still matter.
A bad update process can create issues like:
- old containers still running
- new image built but not used
- missing environment variables
- wrong container name
- duplicate containers
- logs hard to find
- no clean restart path
- forgotten cleanup commands
Helper scripts reduce that friction.
What This Setup Is Meant To Prove
- deployment should be repeatable, not based on memory
- small services still need start, stop, restart, logs, and cleanup flows
- scripts can reduce mistakes without introducing a full CI/CD platform
- environment files should be handled consistently
- Docker commands should be wrapped only where it improves reliability
- service updates should be easy to test and roll back mentally
- good local tooling matters even for small personal or freelance projects
Tools and Areas Used
Docker Layer
- Dockerfile
- Docker image build
- container run
- container stop/remove
- restart policy
- environment file loading
- logs
- container status
- image cleanup
Script Layer
- shell scripts
- help command
- argument handling
- status output
- safe stop/restart flow
- consistent naming
- one-command deployment actions
Service Layer
- small Node.js service
- bot or worker process
- environment variables
- persistent configuration
- logs and error output
- service verification after restart
Intended Build
The intended result is a small deployment helper script or script folder that provides common service operations.
A finished setup should allow:
- build the image
- run the service
- stop the service
- restart the service
- view logs
- show status
- clean old containers/images where appropriate
- print help
- avoid typing long Docker commands manually
- keep naming consistent across updates
The helper should make the service easier to maintain without becoming a large deployment framework.
Recommended Script Commands
A practical script should support commands like:
help
build
run
stop
restart
logs
status
clean
rebuild
The exact command names can change, but the purpose should stay clear.
Example Project Shape
A simple service folder can look like this:
service-name/
Dockerfile
package.json
src/
.env.local
.env.example
scripts/
service.sh
Or, for a smaller project:
service-name/
Dockerfile
.env.local
deploy.sh
The important part is that the script lives near the service and documents the expected workflow.
Environment File Handling
A helper script should use one predictable environment file.
Example:
.env.local
The script should check that it exists before running the container.
Example behavior:
if .env.local is missing:
print a clear error
stop before running the service
This is better than starting a broken container that exits immediately because the token, API key, or config value is missing.
Container Naming
The script should define a clear container name.
Example:
CONTAINER_NAME="service-name"
IMAGE_NAME="service-name:latest"
This avoids random Docker-generated names and makes logs/status/restart commands predictable.
Without consistent names, maintenance becomes harder.
Build Command
The build command should create or update the Docker image.
Example direction:
docker build -t service-name:latest .
A helper script should print what it is doing:
[+] Building service-name:latest
This makes script output easier to read.
Run Command
The run command should start a new container with the correct configuration.
Common options:
--name
--env-file
--restart unless-stopped
--network
-d
For a small outbound-only bot or worker, public ports may not be needed.
Example direction:
docker run -d --name service-name --env-file .env.local --restart unless-stopped service-name:latest
If the service requires host networking, the script should make that explicit.
Stop Command
The stop command should stop and remove the running container cleanly.
Example direction:
docker stop service-name
docker rm service-name
The script should not fail noisily if the container does not exist. It can print a clear message instead.
Restart Command
The restart command should be predictable.
Two common meanings exist:
Restart existing container
docker restart service-name
This is fast, but it does not rebuild or use new code.
Recreate container from current image
stop → run
This is useful after rebuilding the image.
The script should make the behavior clear.
For code updates, the safer flow is usually:
build → stop → run → logs/status
Rebuild Command
A useful command is:
rebuild
Meaning:
build image
stop old container
run new container
show logs/status
This is the command used after changing code.
It avoids the common mistake of building an image but forgetting to recreate the container.
Logs Command
Logs should be easy to reach.
Example direction:
docker logs -f service-name
A helper command like this is useful:
./deploy.sh logs
Logs are the first place to check after a restart.
Status Command
Status should show whether the container is running.
Example direction:
docker ps -a --filter name=service-name
A good script can also show:
- container name
- image name
- running/exited state
- restart count if available
- recent logs if useful
Clean Command
Cleanup should be careful.
A clean command can remove old stopped containers or unused images, but it should not delete unrelated Docker resources without warning.
Good cleanup behavior:
- remove this service’s stopped container
- remove this service’s old image if intended
- avoid global destructive cleanup by default
Avoid making clean run something dangerous like:
docker system prune -a
unless the script clearly asks for confirmation.
Example Script Direction
This is a simplified example shape:
#!/bin/sh
APP_NAME="service-name"
IMAGE_NAME="$APP_NAME:latest"
CONTAINER_NAME="$APP_NAME"
ENV_FILE=".env.local"
case "$1" in
build)
docker build -t "$IMAGE_NAME" .
;;
run)
if [ ! -f "$ENV_FILE" ]; then
echo "Missing $ENV_FILE"
exit 1
fi
docker run -d --name "$CONTAINER_NAME" --env-file "$ENV_FILE" --restart unless-stopped "$IMAGE_NAME"
;;
stop)
docker stop "$CONTAINER_NAME" 2>/dev/null || true
docker rm "$CONTAINER_NAME" 2>/dev/null || true
;;
restart)
"$0" stop
"$0" run
;;
rebuild)
"$0" build
"$0" stop
"$0" run
"$0" logs
;;
logs)
docker logs -f "$CONTAINER_NAME"
;;
status)
docker ps -a --filter "name=$CONTAINER_NAME"
;;
help|*)
echo "Usage: $0 {build|run|stop|restart|rebuild|logs|status|help}"
;;
esac
This is not meant to be universal. It is a starting pattern that should be adapted to each service.
Practical Decisions
Keep commands small and obvious
A deployment script should be boring.
If the script becomes too clever, it becomes another thing to debug.
Make rebuild different from restart
Restarting a container does not apply new code if the image was rebuilt but the container was not recreated.
A separate rebuild command makes this clearer.
Check for env file before running
Fail early if required configuration is missing.
This avoids containers exiting with unclear errors.
Avoid global cleanup by default
Cleanup should target the service unless the user explicitly asks for a full Docker cleanup.
This protects other containers on the same machine.
Print readable status messages
A few simple messages make script output much easier to understand.
Example:
[+] Building image
[+] Stopping old container
[+] Starting new container
[+] Showing logs
Keep one main deployment path
Avoid having multiple conflicting ways to run the same service.
If Docker is the deployment path, the script should be the normal way to manage it.
Common Failure Points
Image Built but Old Code Still Runs
Cause:
docker build was run, but the old container was not recreated
Fix:
build → stop → run
or:
rebuild
Container Name Already Exists
Cause:
old stopped container still exists
Fix:
docker rm service-name
A good stop command should handle this.
Missing Environment Variables
Symptoms:
- service exits immediately
- token/API errors
- config undefined
- bot does not login
- API server fails on startup
Fix:
- check
.env.local - check
--env-file - check variable names
- check application config loading
Logs Show Nothing Useful
Causes:
- app does not log startup errors
- process exits before logging
- logs checked for wrong container
- container name mismatch
- service redirects logs somewhere else
Fix:
- use consistent container names
- log startup config without secrets
- check
docker ps -a - check
docker logs
Docker Architecture Issues
On ARM devices, the image architecture matters.
Symptoms:
- exec format error
- container exits immediately
- binary cannot run
Fix direction:
- build for the target architecture
- use base images that support the device
- avoid forcing the wrong platform unless intentionally emulating
Storage Filling Up
Docker images and containers can take space over time.
Useful checks:
docker images
docker ps -a
docker system df
Cleanup should be done carefully.
What A Finished Setup Should Show
A strong finished setup should show:
- Dockerfile present
.env.exampledocumented.env.localused locally but not committed- helper script with help output
- build command works
- run command works
- restart/rebuild behavior clear
- logs command works
- status command works
- cleanup command is safe
- README explains normal workflow
- service survives reboot if restart policy is used
- no manual long command required for common actions
Evidence Worth Capturing
Useful evidence for this note would include:
- script file
- help output
- Dockerfile
.env.example- build output
docker psoutput- logs output
- rebuild flow screenshot
- failed container example and fix
- README deployment section
- notes about architecture/platform if running on ARM
Technical Assumptions
This setup assumes the service is small enough to run comfortably on the target machine.
It also assumes that Docker is already installed and working.
The script does not replace understanding Docker. It only makes repeated operations consistent.
Key Risks
- hiding too much Docker behavior behind a script
- script deleting unrelated containers or images
- environment file accidentally committed
- old container using old code
- container name mismatch
- missing logs
- no rollback plan
- architecture mismatch on ARM devices
- disk usage growing over time
- script becoming more complicated than the service
Current State
This note represents the deployment maintenance pattern used for small services.
The value is in creating a simple operational loop:
edit → build → stop old container → run new container → check logs
That loop is enough for many small services without needing a full CI/CD system.
What This Note Does Not Claim
This note does not claim that helper scripts replace proper deployment platforms.
It does not claim that Docker is required for every small service.
It does not claim that this is a production-grade orchestration system.
It is a practical pattern for keeping small Docker services maintainable.
Practical Takeaway
A Docker deployment script is useful when it prevents repeated mistakes.
The useful parts are:
- consistent image name
- consistent container name
- predictable env file
- one command for rebuilds
- easy logs
- safe stop/remove flow
- clear help output
- no unnecessary magic
That is enough to make small service deployment much less annoying.